Programar un buscador con PHP y MySQL
Muchas veces hemos querido programar un buscador para nuestra página web y no hemos sabido muy bien como afrontar este problema. Una de las opciones que nos permite MySQL es la búsqueda fulltext, que es un tipo de búsqueda especial para los campos de texto (disponible solamente para el motor MyISAM). Con este tipo de búsqueda logramos incluso que los resultados se ordenen por relevancia.
Los campos que se pueden indexar en modo fulltext son los de tipo char, varchar y text.
A partir de ahora os voy a mostrar un ejemplo de este tipo de búsqueda. Para ello he creado la tabla blog con los siguientes campos:
En primer lugar necesito crear un índice fulltext en los campos sobre los que voy a realizar la búsqueda.
La sintaxis es la siguiente:
alter table tabla add fulltext(campo); alter table tabla add fulltext(campo1,campo2); /*Se pueden combinar varios campos para el índice.*/
y en mi caso usaré:
ALTER TABLE blog ADD FULLTEXT ( articulo, titulo );
Muchos de vosotros os estareis preguntando para que tanto lio, si este mismo problema lo podríamos estar resolviendo
con una simple consulta como la siguiente:
SELECT * FROM blog WHERE articulo LIKE '%$busqueda%' OR titulo LIKE '%$busqueda%'
En primer lugar decir que esta búsqueda es correcta, pero tiene sus limitaciones, pues esta consulta nos mostrará
todos los artículos que en su titulo o en su contenido aparezca la frase de búsqueda tal y como nosotros la introducimos. Esto es muy limitado ya que un cambio en el orden de las palabras que introdujimos en la búsqueda dará al traste con nuestra
búsqueda no mostrando los resultados deseados.
Si bien podríamos depurar y mejorar la sintaxis de nuestra búsqueda utilizando el operador LIKE, pero jugando con esta técnica, las búsquedas resultarían muy lentas
La solución mas eficiente es utilizar los índices FULLTEXT específicamente indicados para estos menesteres y que he comentado anteriormente.
Ahora la consulta para realizar la búsqueda sería:
SELECT * FROM ARTICULOS WHERE MATCH(titulo, articulo) AGAINST ('$busqueda')
Esta query utiliza la función MATCH … AGAINST… que encuentra el texto buscado, usando consultas en lenguaje natural parecido a como lo hacen los motores de búsqueda. Además, calcula internamente una puntuación en función de como aparecen los términos buscados dentro de nuestro artículo.
Como comenté al principio del tutorial, los resultados se ordenen por relevancia. Para ello debemos retocar la query anterior:
SELECT * , MATCH (titulo,articulo) AGAINST ('$busqueda') AS puntuacion FROM blog WHERE MATCH (titulo, articulo) AGAINST ('$busqueda') ORDER BY puntuacion DESC LIMIT 50
Esta query devuelve los 50 primeros resultados encontrados ordenados por orden de relevancia.
El valor de la puntuación es un número decimal comprendido entre 0 y 1, que se calcula a través de un algoritmo interno de la base de datos por cada ocurrencia del patrón de búsqueda, que se irá sumando si ese patrón es encontrado en varias ocasiones.
Mientras que las búsquedas de cadenas formadas por varias palabras son rapidisimas con el operador MATCH …AGAINST, uno de los problemas que tiene dicho operador es que suele fallar cuando el término a buscar contiene simplemente una sola palabra. La solución consiste en chequear el número de palabras a buscar, utilizando una búsqueda simple con LIKE en el caso de una sola palabra, y el método MATCH…AGAINST en el caso de varias:
$trozos = explode(" ",$busqueda); //CUENTA EL NUMERO DE PALABRAS $numero = count($trozos); if ($numero==1) { //SI SOLO HAY UNA PALABRA DE BUSQUEDA SE ESTABLECE UNA INSTRUCION CON LIKE $consulta="SELECT autor,fecha,id,titulo,articulo FROM blog WHERE titulo LIKE '%$busqueda%' OR articulo LIKE '%$busqueda%' LIMIT 50"; } elseif ($numero>1) { //SI HAY UNA FRASE SE UTILIZA EL ALGORTIMO DE BUSQUEDA AVANZADO DE MATCH AGAINST $consulta="SELECT autor,fecha,id,titulo,articulo, MATCH ( titulo, articulo ) AGAINST ( '$busqueda' ) AS puntuacion FROM blog WHERE MATCH ( titulo , articulo ) AGAINST ( '$busqueda' ) ORDER BY puntuacion DESC LIMIT 50"; }
Si queremos que todas las palabras se encuentren en todos los resultados deberemos utilizar el modo booleano:
SELECT * , MATCH (titulo,articulo) AGAINST ('$busqueda') AS puntuacion FROM blog WHERE MATCH (titulo, articulo) AGAINST ('$busqueda' IN BOOLEAN MODE) ORDER BY puntuacion DESC LIMIT 50
aunque en este caso los resultados no se ordenan por relevancia.
En ‘modo booleano’ se pueden aplicar también modificadores, por ejemplo, si queremos las tuplas con contenido ‘texto’ y sin ‘basura’.
SELECT * FROM tabla WHERE match(campo) against('+texto -basura' in boolean mode);
También se puede utilizar el wildcard ‘*’.
SELECT * FROM tabla WHERE match(campo) against('text*' in boolean mode);