Cuando realizamos búsquedas de texto completo, usualmente nos basamos en el acierto que puede tener la frase dentro de la colección de documentos que queremos revisar. Pero, si no conocemos su contenido ¿Cómo podemos tener la certeza de estar buscando en todos los documentos importantes?.

El empleo de la lógica booleana (o lógica de proposiciones) dentro de los elementos de la frase es una práctica que -bien utilizada- puede ayudarnos a mejorar la relación entre el número de resultados relevantes y la precisión de estos, con el fin de obtener el conjunto resultante que deseamos.

Veamos un ejemplo, vamos a realizar una búsqueda para encontrar los documentos que hablan sobre la estructura de directorios de Laravel, utilizando el lenguaje natural y la frase Laravel: http://127.0.0.1:8000/?search=Laravel.

Agregamos más datos de prueba al seeder para que la búsqueda tenga mayor relevancia, puedes ver este cambio en el repositorio. Recuerda ejecutar las migraciones y seeder nuevamente.

Esto nos devuelve 6 registros (ordenados por relevancia) donde se menciona la palabra Laravel. El documento más importante tiene un score de 0.9072468280792236 y a pesar de ser el más relevante, no es el más preciso; pues nuestro objetivo es el documento sobre el tema de Estructura de directorios.

Ahora, si añadimos otra palabra clave a la frase: http://127.0.0.1:8000/?search='Laravel estructura', obtenemos como resultado de mayor relevancia el documento que estamos esperando, con un score más alto que en la consulta anterior (3.444157838821411).

Mientras más palabras claves utilices, mayor será la relevancia de la frase.

Sin embargo, aquí tenemos el primer problema: aunque filtremos con otra palabra no se reduce el conjunto resultante, es decir, seguimos recibiendo los mismos 6 registros.

Incluso, si somos más precisos en la frase obtenemos el mismo comportamiento: http://127.0.0.1:8000/?search='Laravel estructura directorios', logramos aumentar el score del primer resultado pero no reducir la cantidad del conjunto devuelto.

Afortunadamente existe una forma de lograr esto de manera sencilla, y es usando:

El modo de búsqueda booleano

El modo booleano nos permite mejorar la precisión en búsquedas de texto completo, al introducir ciertos caracteres especiales al principio o al final de las palabras que forman la frase de búsqueda. Entre los operadores lógicos tenemos:

  • AND: Se representa anteponiendo el operador +.
  • NOT: Se representa anteponiendo el operador -.
  • OR: No es necesario anteponer ningún operador.

Para realizar búsquedas de texto completo booleanas en nuestro proyecto, debemos cambiar la función AGAINST() para agregar el modificador IN BOOLEAN MODE:

// routes/web.php

Route::get('/', function () {
    return Chapter::query()
        ->when(request('search'), function ($query, $search) {
            $query->select('id', 'title', 'content')
                ->selectRaw(
                    'match(title,content) against(? in boolean mode) as score',
                    [$search]
                )
                ->whereRaw(
                    'match(title,content) against(? in boolean mode) > 0.0000001',
                    [$search]
                );
        })
        ->get();
});
Aprende a manejar tus bases de datos con el ORM de Laravel en nuestro Curso de Eloquent.

Ver más

Pasar caracteres especiales en la solicitud HTTP

Utilizar caracteres especiales como parte de una solicitud HTTP puede generar resultados inesperados si no son codificados correctamente dentro de la URL para representarlos.

Para observar esto utilizaremos una herramienta de depuración llamada Telescope, la cual te enseñamos en este tutorial.

Por ejemplo, vamos a intentar filtrar una consulta con el operador AND para retornar sólo los resultados que tienen todas las palabras de la frase.

Si omitimos la codificación correcta del parámetro y realizamos la búsqueda: http://127.0.0.1:8000/?search=+Laravel +estructura +directorios, vamos a obtener un conjunto resultante no deseado.

Esto sucede porque el parámetro ha sido normalizado para la sintaxis URL, es decir, sanitizado por Laravel; y el símbolo + es sustituido por %20. Podemos comprobarlo al revisar la petición en Telescope:

Detalles de consulta en telescope

%20 es el código unicode o utf-8 para representar el espacio en blanco en una URL.

Nota que el campo search en la petición no tiene el operador + y esto mismo se refleja en el código SQL de la consulta construido por Eloquent. En otras palabras, la búsqueda booleana se realizó pero de forma opcional con el operador OR.

Podemos solventar este detalle de forma sencilla al representar el operador + en la solicitud de esta manera: http://127.0.0.1:8000/?search=%2BLaravel %2Bestructura %2Bdirectorios

Puedes apoyarte en Telescope para revisar el operador en el parámetro de la petición y la consulta SQL ejecutada.

Los resultados de las búsquedas booleanas

Aunque podemos crear nuestra frase de búsqueda con varias palabras claves para filtrar los resultados, esto no será suficiente si queremos reducir ampliamente la cantidad de registros o documentos retornados. Por lo que resulta útil utilizar otro tipo de condiciones lógicas para descartar documentos no relevantes y conservar aquellos que contengan la información que deseamos.

Los operadores booleanos

Además de los operadores +, -, y el no operador. La lista total de operadores la completan los siguientes caracteres especiales: @, >, <, ( ), ~, * y ", cuyos significados veremos a continuación:

El operador lógico AND

Está representado por el símbolo + que se antepone a la palabra, como vimos en el ejemplo anterior.

El operador lógico OR

En ocasiones, la cantidad de documentos devueltos puede ser pequeña y tal vez quisiéramos revisar por otros documentos existentes, en estos casos podemos utilizar el operador OR. En el modo booleano este operador se representa dejando la palabra sola, es decir, sin anteponer ningún caracter; lo que hace que dicha palabra sea opcional, como en las búsquedas en lenguaje natural.

Por ejemplo, si ejecutamos una consulta como la siguiente: http://127.0.0.1:8000/?search=%2Binstalación %2Blaravel, nuestro resultado sería unicamente los documentos que hablan sobre la instalación de Laravel.

Pero si deseamos obtener más documentos relacionados a Laravel, además de la instalación, lo que debemos hacer es volver la búsqueda menos estricta, dejando la palabra instalación sin el operador + para que sea opcional: http://127.0.0.1:8000/?search=instalación %2Blaravel.

Observa que el conjunto resultante paso de 2 a 6 resultados.

El operador NOT

Está representado por el caracter - y se usa para excluir de los resultados aquellos documentos que poseen dicha palabra.

Por ejemplo, imagina que en la consulta anterior deseamos quitar los documentos que hablen sobre Composer, para ello la búsqueda quedaría de esta manera: http://127.0.0.1:8000/?search=instalación %2BLaravel -composer.

Ahora, los primeros 3 resultados que aparecían en la consulta han sido excluidos, dado que su contenido hablaba sobre Composer.

No necesitas escapar el símbolo - porque no es considerado una palabra reservada.

El operador @distance

Permite determinar si dos o más palabras se encuentran a una distancia menor o igual a un valor dado. Las palabras son colocadas en una cadena encerrada entre comillas dobles y se incluyen en el conteo. Veamos un ejemplo.

Supongamos que deseamos buscar los documentos que hablen de Valet pero sólo para la versión del sistema operativo Mac. Además, queremos establecer que estén mencionadas en el texto con una separación de máximo 13 palabras.

Podemos lograrlo con: http://127.0.0.1:8000/?search="Valet Mac" @13. Esta consulta nos devolverá los documentos donde aparezcan Valet y Mac separadas por 13 palabras o menos.

Recuerda que las palabras en la cadena de búsqueda entran en el conteo, por lo que Valet es la número 1. Así que para obtener resultados, Mac como máximo puede ser la número 13.

Este tipo de consultas es ideal cuando buscas el nombre de una persona o el nombre de una compañía.

Los paréntesis ()

Los paréntesis permiten escribir expresiones más complejas y el anidamiento de sub-expresiones. Podemos utilizarlos cuando combinamos operadores AND y OR.

Por ejemplo, para obtener los documentos que contienen las palabras contenedor y servicios, pero también los que contienen las palabras contenedor e inyección podríamos realizar la siguiente consulta: http://127.0.0.1:8000/?search=%2Bcontenedor %2B(servicios inyección)

Los paréntesis no se necesitan codificar en la url.

Aprende diversos tips para optimizar el rendimiento de tu consultas SQL con Laravel con nuestro Curso de Optimización con Eloquent.

Ver más

Operadores que inciden sobre la relevancia

Nos permiten establecer una distinción sobre la importancia que tienen las palabras en una frase. Para indicar una mayor prioridad utilizamos el símbolo mayor que (>) y si queremos restarle relevancia lo representamos con el símbolo menor que (<). Veamos un ejemplo.

Al realizar la siguiente consulta: http://127.0.0.1:8000/?search=%2Blaravel, nota que los dos últimos resultados tienen el mismo score. Podríamos indicar que el documento sobre el contenedor de servicios tiene una relevancia mayor sobre el documento del ciclo de vida de una solicitud http, usando los operadores en la frase de esta manera: http://127.0.0.1:8000/?search=%2Blaravel %2B(>contenedor <ciclo).

Ahora, observa como el score del contenedor de servicios aumentó a 4.081259250640869, mientras que el de ciclo de vida disminuyó a 0.6313542723655701.

Los símbolos > y < se pueden colocar directamente en la url.

La negación o la tilde ~

La tilde actúa como el operador de negación, causando que la contribución de la palabra a la relevancia de la fila sea negativa. Esto es útil para marcar palabras «noise» o que sólo son ruidosas. Una fila que contiene una palabra de este tipo es «rankeada» más baja que las demás que no la contienen, pero no es excluida del conjunto resultante como hace el operador NOT (-).

Por ejemplo, cuando consultamos http://127.0.0.1:8000/?search=Laravel, el registro más relevante es el de Instalación de Laravel con un score de  0.9072468280792236.

Pero, si aplicamos el operador de negación: http://127.0.0.1:8000/?search=Laravel ~instalación, el score del documento bajará a 0.7228183746337891.

El operador asterisco *

Este operador (*) nos permite encontrar en el texto de los documentos aquellas palabras que comiencen con las letras que preceden al asterisco. Por lo tanto, se agrega al final de la palabra que deseamos buscar. Veamos un ejemplo.

Supongamos que deseamos obtener todos los documentos que se relacionen con la instalación, es decir, aquellos que contengan palabras como: instala, instalar, instalación, instalador.

El operador asterisco nos brinda una manera muy sencilla de lograrlo, sin escribir una frase con todo el contenido. Solo debemos realizar una consulta que filtre los documentos que contenga en el texto alguna palabra que inicie por instal, de la siguiente manera: http://127.0.0.1:8000/?search=instal*.

Este operador proporciona una implementación de una técnica llamada stemming. Esto hace que aumente el número de documentos que se pueden encontrar en una consulta.

El operador de comillas " "

Al igual que en el modo de lenguaje natural, si realizamos una consulta en modo booleano de una frase encerrada entre comillas dobles " ", ésta nos retornará solamente los registros que contengan la frase exacta.

Por ejemplo: http://127.0.0.1:8000/?search="En su lugar es un punto de partida".

La frase se divide en palabras las cuales se buscan en el índice FULLTEXT, los delimitadores de palabras tales como , . ; : al no poseer significado no necesitan coincidir exactamente.

Ventajas del modo de búsqueda booleano

A diferencia del lenguaje natural, las búsquedas de texto completo utilizando el modo booleano nos conducen un paso más adelante, ya que nos permiten manipular la cantidad de resultados devueltos por medio de los operadores lógicos tradicionales. También podemos mejorar la precisión utilizando los operadores que inciden sobre la relevancia.

Otra de sus ventajas es que podemos hacer búsquedas por stemming a través del operador asterisco, para extraer todos los registros posibles asociados a la raíz de una palabra sin tener que escribir todas las opciones. Además, podemos construir expresiones de búsquedas poderosas utilizando los paréntesis y el anidamiento de expresiones que, a fin de cuenta, no son más que frases.

Material relacionado:

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Búsquedas de texto completo e índices FULLTEXT en Laravel Lección siguiente Búsquedas de texto completo con el modo Query Expansion en Laravel