sql >> Base de Datos >  >> RDS >> Mysql

¿Cómo encontrar resultados similares y ordenar por similitud?

Descubrí que la distancia de Levenshtein puede ser buena cuando busca una cadena completa contra otra cadena completa, pero cuando busca palabras clave dentro de una cadena, este método no devuelve (a veces) los resultados deseados. Además, la función SOUNDEX no es adecuada para otros idiomas además del inglés, por lo que es bastante limitada. Podría salirse con la suya con LIKE, pero en realidad es para búsquedas básicas. Es posible que desee buscar otros métodos de búsqueda para lo que desea lograr. Por ejemplo:

Puede usar Lucene como base de búsqueda para sus proyectos. Está implementado en la mayoría de los principales lenguajes de programación y es bastante rápido y versátil. Este método es probablemente el mejor, ya que no solo busca subcadenas, sino también transposición de letras, prefijos y sufijos (todos combinados). Sin embargo, debe mantener un índice separado (aunque usar CRON para actualizarlo desde un script independiente de vez en cuando funciona).

O, si desea una solución MySQL, la funcionalidad de texto completo es bastante buena y ciertamente más rápida que un procedimiento almacenado. Si sus tablas no son MyISAM, puede crear una tabla temporal y luego realizar su búsqueda de texto completo:

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 ;

Usa un generador de datos para generar algunos datos aleatorios si no quiere molestarse en crearlos usted mismo...

** NOTA **:el tipo de columna debe ser latin1_bin para realizar una búsqueda sensible a mayúsculas y minúsculas en lugar de mayúsculas y minúsculas con latin1 . Para cadenas Unicode, recomendaría utf8_bin para mayúsculas y minúsculas y utf8_general_ci para búsquedas que no distinguen entre mayúsculas y minúsculas.

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`;

Obtenga más información al respecto en la página de referencia de la API de MySQL

La desventaja de esto es que no buscará la transposición de letras o palabras "similares, que suenan como".

** ACTUALIZAR **

Usando Lucene para su búsqueda, simplemente necesitará crear un trabajo cron (todos los servidores web tienen esta "característica") donde este trabajo simplemente ejecutará un script PHP (por ejemplo, "cd /path/to/script; php searchindexer.php" ) que actualizará los índices. El motivo es que la indexación de miles de "documentos" (filas, datos, etc.) puede llevar varios segundos, incluso minutos, pero esto es para garantizar que todas las búsquedas se realicen lo más rápido posible. Por lo tanto, es posible que desee crear un trabajo de retraso para que lo ejecute el servidor. Puede ser durante la noche, o en la próxima hora, esto depende de ti. El script PHP debería verse así:

$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

Entonces, básicamente así es como buscas (búsqueda básica):

$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.
}

Estos son excelentes sitios sobre Lucene en Java , PHP y .Net .

En conclusión cada método de búsqueda tiene sus pros y sus contras:

  • Mencionaste búsqueda Sphinx y se ve muy bien, siempre que pueda hacer que el deamon se ejecute en su servidor web.
  • Zend Lucene requiere un trabajo cron para volver a indexar la base de datos. Si bien es bastante transparente para el usuario, esto significa que los datos nuevos (¡o los datos eliminados!) no siempre están sincronizados con los datos de su base de datos y, por lo tanto, no aparecerán de inmediato en la búsqueda del usuario.
  • La búsqueda de MySQL FULLTEXT es buena y rápida, pero no le brindará toda la potencia y flexibilidad de las dos primeras.

Por favor, siéntase libre de comentar si he olvidado/perdido algo.