|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Búsquedas de texto con Postgresql" |
| 4 | +date: 2019-11-27 9:00:00 |
| 5 | +author: ugaitz |
| 6 | +categories: sql,postgre,base de datos,bbdd |
| 7 | +tags: sql,postgre,base de datos,bbdd |
| 8 | +header-image: post-headers/data.jpg |
| 9 | +--- |
| 10 | + |
| 11 | +Hay veces en los que nos encontramos con la necesidad de buscar un registro en una base de datos partiendo de alguna palabra que uno de los campos de la tabla contiene. Si la búsqueda que queremos realizar es muy sencilla, normalmente será suficiente con hacer un _LIKE_ en la consulta. Pero si la consulta que queramos realizar es un poco compleja, veremos que utilizar _LIKE_ no será viable. |
| 12 | + |
| 13 | +## Buscar con _LIKE_ |
| 14 | +Cuando queremos buscar una sola palabra sobre un texto _natural_, como podría ser el contenido de una noticia, la búsqueda con _LIKE_ se complica ya que hay muchos matices que hay que tener en cuenta: |
| 15 | + |
| 16 | +- Si buscamos utilizando _LIKE '%palabra%'_ la consulta también devolverá palabras que contengan lo que se ha buscado, en este ejemplo se ve que si buscamos _texto_ en _contexto_, nos devuelve true. |
| 17 | + |
| 18 | +``` |
| 19 | +SELECT 'contexto' LIKE '%texto%' as contiene |
| 20 | +``` |
| 21 | + |
| 22 | +{: .center } |
| 23 | + |
| 24 | +- Si le añadimos unos espacios para que sea _LIKE '% palabra %'_, cuando la palabra que buscamos está al final de una frase terminando con un punto _(.)_ o es un texto inicial, no lo encontrará: |
| 25 | + |
| 26 | +``` |
| 27 | +SELECT 'texto texto.' LIKE '% texto %' as contiene |
| 28 | +``` |
| 29 | +{: .center } |
| 30 | + |
| 31 | +Para que este tipo de búsqueda de texto funcione correctamente, tendríamos que utilizar una expresión complicada que haría que nuestra consulta tardara más de lo debido. Y además de esta complejidad, ¿que pasa con las palabras en singular o plural? ¿Cuanto se nos complicaría la expresión para realizar estas búsquedas correctamente? |
| 32 | + |
| 33 | +## Buscar con _tsvector_ y _tsquery_ |
| 34 | + |
| 35 | +Para solucionar este tipo de problemas se suele recurrir a herramientas como _Elastic search_. Pero si tenemos una base de datos _PostgreSQL_ y no necesitamos todo el potencial de este tipo de herramientas, en Postgresql nos encontramos con un tipo de dato que podría evitarnos quebraderos de cabeza. |
| 36 | + |
| 37 | +### tsvector |
| 38 | + |
| 39 | +Con la función _to_tsvector_ podemos transformar un texto al tipo de dato _tsvector_ separando las palabras, convirtiendolo en lexemas y creando índices optimizados para las búsquedas de texto. Al realizar esta transformación tiene en cuenta la configuración del idioma del servidor, por ejemplo: |
| 40 | + |
| 41 | +``` |
| 42 | +SELECT to_tsvector('Grumpy wizards make toxic brew for the evil queen and Jack'); |
| 43 | +``` |
| 44 | + |
| 45 | +{: .center } |
| 46 | + |
| 47 | +En la transformación se puede ver que ha elimado las palabras _for_, _the_ y _and_ y ha transformado otras como _Grumpy_ o _wizards_. En este caso ha funcionado correctamente por que el texto escrito estaba en inglés, que es el mismo idioma en el que está configurado nuestra base de datos. ¿Qué pasaría si pusieramos texto en castellano? |
| 48 | + |
| 49 | +``` |
| 50 | +SELECT to_tsvector('El veloz murciélago hindú comía feliz cardillo y kiwi'); |
| 51 | +``` |
| 52 | +{: .center } |
| 53 | + |
| 54 | +Cómo el idioma por defecto de PostgreSQL es el inglés, el parseo no ha ido muy bien. Sigue apareciendo _el_ y _y_ que no deberían aparecer, por eso el método _to_tsvector_ acepta el parámetro de idioma, por lo que se podría hacer lo siguiente: |
| 55 | + |
| 56 | +``` |
| 57 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi'); |
| 58 | +``` |
| 59 | +{: .center } |
| 60 | + |
| 61 | +Al haber hecho la transformación se puede ver que ha eliminado palabras como _y_ y _el_ y ha modificado otras quitando tildes, generos y conjugación de verbos. |
| 62 | + |
| 63 | +### tsquery |
| 64 | + |
| 65 | +Para crear querys y buscar en _tsvector_ está el tipo de dato _tsquery_. Del mismo modo que _to_tsvector_ tenemos el método _to_tsquery_. Con este método convertiremos querys para buscar palabras o textos a un formato adecuado para buscar en tsvector. Es posible pasar el método de idioma del mismo modo que con _to_tsvector_. |
| 66 | + |
| 67 | +``` |
| 68 | +SELECT to_tsquery('comer') as english, to_tsquery('spanish','comer') as spanish; |
| 69 | +``` |
| 70 | + |
| 71 | +{: .center } |
| 72 | + |
| 73 | +En este caso, el verbo _comer_ lo ha modificado también a _com_, lo que hará que al buscar _comer_ en un texto donde pone _comía_ lo encuentre. Para realizar busquedas, utilizaremos dos arrobas _(@@)_ entre _tsvector_ y _tsquery_ |
| 74 | + |
| 75 | +``` |
| 76 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'comer') as encontrado |
| 77 | +``` |
| 78 | + |
| 79 | +{: .center } |
| 80 | + |
| 81 | +Si tuvieramos una tabla _documentos_ con una columna _documento_tsvector_ de tipo _tsvector_ y quisieramos encontrar solamente los registros con la palabra _comer_, podríamos hacerlo la siguiente forma: |
| 82 | + |
| 83 | +``` |
| 84 | +SELECT * FROM documentos WHERE documento @@ tsquery('spanish', 'comer') |
| 85 | +``` |
| 86 | + |
| 87 | +_tsquery_ ofrece multiples posibilidades a la hora de buscar, aquí ponemos los que nos parecen más relevantes: |
| 88 | + |
| 89 | +- _and (&)_: Si queremos que el texto contenga varias palabras |
| 90 | + |
| 91 | +``` |
| 92 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'comer & feliz') as encontrado |
| 93 | +``` |
| 94 | +{: .center } |
| 95 | + |
| 96 | +``` |
| 97 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'comer & triste') as encontrado |
| 98 | +``` |
| 99 | +{: .center } |
| 100 | + |
| 101 | +- _or_ (\|): Si queremos que el texto contenga una de las palabras |
| 102 | + |
| 103 | +``` |
| 104 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'comer | triste') as encontrado |
| 105 | +``` |
| 106 | +{: .center } |
| 107 | + |
| 108 | +``` |
| 109 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'cenar | triste') as encontrado |
| 110 | +``` |
| 111 | +{: .center } |
| 112 | + |
| 113 | +- _<->_: Si queremos que tenga dos palabras consecutivas |
| 114 | + |
| 115 | +``` |
| 116 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'veloz <-> murciélago') as encontrado |
| 117 | +``` |
| 118 | +{: .center } |
| 119 | + |
| 120 | +``` |
| 121 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'veloz <-> hindú') as encontrado |
| 122 | +``` |
| 123 | +{: .center } |
| 124 | + |
| 125 | +- _<N>_: Si buscamos dos palabras que esten a una distancia de _N_ |
| 126 | + |
| 127 | +``` |
| 128 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'veloz <2> hindú') as encontrado |
| 129 | +``` |
| 130 | +{: .center } |
| 131 | + |
| 132 | +``` |
| 133 | +SELECT to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi') @@ to_tsquery('spanish', 'veloz <3> hindú') as encontrado |
| 134 | +``` |
| 135 | +{: .center } |
| 136 | + |
| 137 | +Podemos encontrar más acerca de las posibilidades de _tsquery_ en la [documentación][textsearch-documentation]{:target="_blank"}. |
| 138 | + |
| 139 | +### ts_rank |
| 140 | + |
| 141 | +Por último, comentar que es posible ordenar los artículos por relevancia utilizando _ts_rank_. Este método le asigna un valor numérico a la relevancia de _"acierto"_ de la búsqueda en el texto. Por ejemplo, no es lo mismo que en una búsqueda con _or_ estén las 3 palabras que se buscan, o solo esté una. |
| 142 | + |
| 143 | +``` |
| 144 | +SELECT ts_rank(to_tsvector('spanish','El veloz murciélago hindú comía feliz cardillo y kiwi'),to_tsquery('spanish', 'veloz | murcielago | hindú')); |
| 145 | +``` |
| 146 | +{: .center } |
| 147 | + |
| 148 | +``` |
| 149 | +SELECT ts_rank(to_tsvector('spanish','El veloz canguro australiano comía feliz cardillo y kiwi'),to_tsquery('spanish', 'veloz | murcielago | hindú')); |
| 150 | +``` |
| 151 | +{: .center } |
| 152 | + |
| 153 | +Para aplicar _ts_rank_ en una consulta, podríamos hacerlo de la siguiente manera. |
| 154 | + |
| 155 | +``` |
| 156 | +SELECT * FROM documentos ORDER BY ts_rank(document, tsquery('spanish', 'comer')) DESC |
| 157 | +``` |
| 158 | + |
| 159 | +[textsearch-documentation]: https://www.postgresql.org/docs/9.6/functions-textsearch.html |
0 commit comments