Jag har upptäckt att Levenshtein-avståndet kan vara bra när du söker efter en hel sträng mot en annan hel sträng, men när du letar efter nyckelord inom en sträng ger denna metod inte (ibland) de önskade resultaten. Dessutom är SOUNDEX-funktionen inte lämplig för andra språk än engelska, så den är ganska begränsad. Du kan komma undan med LIKE, men det är egentligen för grundläggande sökningar. Du kanske vill undersöka andra sökmetoder för vad du vill uppnå. Till exempel:
Du kan använda Lucene som sökbas för dina projekt. Det är implementerat i de flesta större programmeringsspråk och det skulle vara ganska snabbt och mångsidigt. Den här metoden är förmodligen den bästa, eftersom den inte bara söker efter delsträngar, utan även bokstavstransponering, prefix och suffix (alla kombinerade). Du måste dock behålla ett separat index (att använda CRON för att uppdatera det från ett oberoende skript då och då fungerar dock).
Eller, om du vill ha en MySQL-lösning, är fulltextfunktionaliteten ganska bra, och säkerligen snabbare än en lagrad procedur. Om dina tabeller inte är MyISAM kan du skapa en tillfällig tabell och sedan utföra din fulltextsökning :
CREATE TABLE IF NOT EXISTS `tests`.`data_table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(2000) CHARACTER SET latin1 NOT NULL,
`description` text CHARACTER SET latin1 NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;
Använd en datagenerator att generera lite slumpmässig data om du inte vill skapa det själv...
** OBS ** :kolumntypen ska vara latin1_bin
att utföra en skiftlägeskänslig sökning istället för skiftlägesokänslig med latin1
. För unicode-strängar skulle jag rekommendera utf8_bin
för skiftlägeskänsliga och utf8_general_ci
för skiftlägesokänsliga sökningar.
DROP TABLE IF EXISTS `tests`.`data_table_temp`;
CREATE TEMPORARY TABLE `tests`.`data_table_temp`
SELECT * FROM `tests`.`data_table`;
ALTER TABLE `tests`.`data_table_temp` ENGINE = MYISAM;
ALTER TABLE `tests`.`data_table_temp` ADD FULLTEXT `FTK_title_description` (
`title` ,
`description`
);
SELECT *,
MATCH (`title`,`description`)
AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE) as `score`
FROM `tests`.`data_table_temp`
WHERE MATCH (`title`,`description`)
AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE)
ORDER BY `score` DESC;
DROP TABLE `tests`.`data_table_temp`;
Läs mer om det från MySQL API-referenssidan
Nackdelen med detta är att det inte letar efter bokstavstransponering eller "liknande, låter som" ord.
** UPPDATERA **
Genom att använda Lucene för din sökning behöver du helt enkelt skapa ett cron-jobb (alla webbhotell har denna "funktion") där detta jobb helt enkelt kommer att köra ett PHP-skript (t.ex. "cd /path/to/script; php searchindexer.php" ) som kommer att uppdatera indexen. Anledningen är att indexering av tusentals "dokument" (rader, data, etc.) kan ta flera sekunder, till och med minuter, men detta är för att säkerställa att alla sökningar utförs så snabbt som möjligt. Därför kanske du vill skapa ett fördröjningsjobb som ska köras av servern. Det kan vara över natten, eller under den närmaste timmen är det upp till dig. PHP-skriptet bör se ut ungefär så här:
$indexer = Zend_Search_Lucene::create('/path/to/lucene/data');
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
// change this option for your need
new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);
$rowSet = getDataRowSet(); // perform your SQL query to fetch whatever you need to index
foreach ($rowSet as $row) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::text('field1', $row->field1, 'utf-8'))
->addField(Zend_Search_Lucene_Field::text('field2', $row->field2, 'utf-8'))
->addField(Zend_Search_Lucene_Field::unIndexed('someValue', $someVariable))
->addField(Zend_Search_Lucene_Field::unIndexed('someObj', serialize($obj), 'utf-8'))
;
$indexer->addDocument($doc);
}
// ... you can get as many $rowSet as you want and create as many documents
// as you wish... each document doesn't necessarily need the same fields...
// Lucene is pretty flexible on this
$indexer->optimize(); // do this every time you add more data to you indexer...
$indexer->commit(); // finalize the process
Sedan är det i princip hur du söker (grundläggande sökning) :
$index = Zend_Search_Lucene::open('/path/to/lucene/data');
// same search options
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);
Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');
$query = 'php +field1:foo'; // search for the word 'php' in any field,
// +search for 'foo' in field 'field1'
$hits = $index->find($query);
$numHits = count($hits);
foreach ($hits as $hit) {
$score = $hit->score; // the hit weight
$field1 = $hit->field1;
// etc.
}
Här är fantastiska sajter om Lucene i Java , PHP , och .Net .
Sammanfattningsvis varje sökmetod har sina egna för- och nackdelar:
- Du nämnde Sfinxsökning och det ser väldigt bra ut, så länge du kan få deamon att köra på ditt webbhotell.
- Zend Lucene kräver ett cron-jobb för att omindexera databasen. Även om det är ganska transparent för användaren, betyder det att all ny data (eller raderad data!) inte alltid är synkroniserad med data i din databas och därför inte dyker upp direkt vid användarsökning.
- MySQL FULLTEXT-sökning är bra och snabb, men ger dig inte all kraft och flexibilitet som de två första.
Kommentera gärna om jag har glömt/missat något.