{"id":14873,"date":"2025-07-14T16:48:12","date_gmt":"2025-07-14T14:48:12","guid":{"rendered":"https:\/\/hopla.tech\/?p=14873"},"modified":"2025-10-22T16:55:25","modified_gmt":"2025-10-22T14:55:25","slug":"optimizacion-busquedas-operador-like-postgresql","status":"publish","type":"post","link":"https:\/\/hopla.tech\/en\/optimizacion-busquedas-operador-like-postgresql\/","title":{"rendered":"We LIKE &#8216;%postgres%&#8217;"},"content":{"rendered":"\n<p id=\"intro\"><a href=\"#intro\">Introducci\u00f3n<\/a><\/p>\n\n\n\n<p><a href=\"#operadorlike\">El operador LIKE<\/a><\/p>\n\n\n\n<p><a href=\"#usolike\">Uso de LIKE en una base de datos con collation \u201cC\u201d<\/a><\/p>\n\n\n\n<p id=\"-intro\"><a href=\"#busquedasinindice\">B\u00fasqueda sin \u00edndice<\/a><\/p>\n\n\n\n<p><a href=\"#busquedaconindice\">B\u00fasqueda con \u00edndice<\/a><\/p>\n\n\n\n<p><a href=\"#diferentedec\">Uso de LIKE en una base de datos con collate diferente de &#8216;C&#8217;<\/a><\/p>\n\n\n\n<p><a href=\"#palabralike\">Uso de LIKE para buscar un patr\u00f3n que contenga una palabra. LIKE &#8216;%postgres%<\/a><\/p>\n\n\n\n<p><a href=\"#trigramas\">Trigramas<\/a><\/p>\n\n\n\n<p><a href=\"#indicesgin\">\u00cdndices GIN<\/a><\/p>\n\n\n\n<p><a href=\"#resumen\">Resumen<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"intro\">Introducci\u00f3n: C\u00f3mo optimizar b\u00fasquedas con el operador LIKE<\/h2>\n\n\n\n<p>En este art\u00edculo, <em>primero<\/em>, vamos a hablar de la b\u00fasqueda de texto con patrones con el operador LIKE.<\/p>\n\n\n\n<p>En concreto explicaremos c\u00f3mo mejorar su desempe\u00f1o con el uso de \u00edndices, \u00edndices Btree.<\/p>\n\n\n\n<p>Adem\u00e1s, hablaremos de qu\u00e9 ocurre cuando usamos un collation diferente de \u201cC\u201d.<\/p>\n\n\n\n<p>Hablaremos de c\u00f3mo extender sus capacidades con el uso de trigramas e \u00edndices GIN, para que sea incluso capaz de usar \u00edndices con patrones tipo \u2018%postgres%\u2019.<\/p>\n\n\n\n<p>Al final del art\u00edculo seremos capaces de hacer b\u00fasquedas por cualquier tipo de dato texto con la eficiencia y velocidad que proporcionan los \u00edndices usando patrones de b\u00fasqueda muy complejos.<\/p>\n\n\n\n<p>\u00a1Empecemos!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"operadorlike\">El operador LIKE: conceptos b\u00e1sicos y ejemplos<\/h2>\n\n\n\n<p><em>En general<\/em>, el operador LIKE, como su nombre indica, sirve para buscar registros cuyo valor se asemeje a un patr\u00f3n de b\u00fasqueda. Se usan dos wildcards para representar expresiones regulares:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El wildcard &#8216;%&#8217; reemplaza cualquier secuencia de cero o m\u00e1s caracteres.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Significado<\/strong><\/th><th><strong>Patr\u00f3n<\/strong><\/th><th><strong>Resultado<\/strong><\/th><\/tr><\/thead><tbody><tr><td>Que empiece por<\/td><td>\u2018postgresql\u2019 LIKE \u2018postgres%\u2019<\/td><td>true<\/td><\/tr><tr><td>Que acabe por<\/td><td>\u2018postgresql\u2019 LIKE &#8216;%sql\u2019<\/td><td>true<\/td><\/tr><tr><td>Que contenga<\/td><td>\u2018postgresql\u2019 LIKE \u2018%gres%\u2019<\/td><td>true<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El wildcard &#8216;_&#8217; reemplaza un \u00fanico car\u00e1cter<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Significado<\/strong><\/th><th><strong>Patr\u00f3n<\/strong><\/th><th><strong>Resultado<\/strong><\/th><\/tr><\/thead><tbody><tr><td>Que la \u00faltima letra sea cualquiera<\/td><td>\u2018postgresql\u2019 LIKE \u2018postgresq_\u2019<\/td><td>true<\/td><\/tr><tr><td>Que la primera letra sea cualquiera<\/td><td>\u2018postgresql\u2019 LIKE \u2018_ostgresql\u2019\u2019<\/td><td>true<\/td><\/tr><tr><td>Que la primera letra y la ultima sean cualquiera<\/td><td>\u2018postgresql\u2019 LIKE \u2018_ostgresq_\u2019<\/td><td>true<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"usolike\"><strong>Uso de LIKE en una base de datos con collation \u201cC\u201d<\/strong><\/h2>\n\n\n\n<p>Para empezar, vamos a probar el operador LIKE en una base de datos cuyo collation sea &#8216;C&#8217;, es decir que el texto se ordena por el valor de los bytes.<\/p>\n\n\n\n<p>Creemos la base de datos en primer lugar. La creamos con encoding ASCII pero ser\u00eda igual con encoding utf8 o latin1<\/p>\n\n\n\n<p>postgres=# CREATE DATABASE like_test ENCODING = &#8216;SQL_ASCII&#8217; LOCALE = &#8216;C&#8217; TEMPLATE = &#8216;template0&#8217;;<br>CREATE DATABASE<\/p>\n\n\n\n<p>Nos conectamos a la base de datos<\/p>\n\n\n\n<p>postgres=# \\c like_test ;<br>psql (14.17 (Ubuntu 14.17-0ubuntu0.22.04.1), server 14.15 (Ubuntu 14.15-1.pgdg22.04+1))<br>You are now connected to database \u00ablike_test\u00bb as user \u00abpostgres\u00bb.<br><\/p>\n\n\n\n<p>Creamos una tabla con un tipo de dato texto<\/p>\n\n\n\n<p>like_test=# CREATE TABLE like_test(field text);<br>CREATE TABLE<\/p>\n\n\n\n<p>Llenamos la tabla con datos de prueba<\/p>\n\n\n\n<p>like_test=# INSERT INTO like_test SELECT md5(a::text) FROM generate_series(1, 1000000) AS a;<br>INSERT 0 1000000<br><\/p>\n\n\n\n<p>Hemos insertado un mill\u00f3n de registros utilizando la funci\u00f3n generate_series(1, 1000000), que emite una tabla con n\u00fameros secuenciales. A cada n\u00famero generado, le hemos aplicado la funci\u00f3n hash md5 para obtener una secuencia de caracteres pseudo-aleatoria. Finalmente insertamos un solo registro con el valor \u2018postgres\u2019 que nos servir\u00e1 para las pruebas.<\/p>\n\n\n\n<p>postgres=# INSERT INTO like_test(field) VALUES (&#8216;postgres&#8217;);<br>INSERT 0 1<\/p>\n\n\n\n<p>Finalmente, ya estamos listos para nuestras pruebas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"busquedasinindice\"><strong>B\u00fasqueda sin \u00edndice: Limitaciones y an\u00e1lisis<\/strong><\/h3>\n\n\n\n<p>Busquemos sin \u00edndice usando EXPLAIN para ver qu\u00e9 estrategia sigue. Desactivamos el escaneo paralelo para hacer m\u00e1s comprensible la lectura del plan.<\/p>\n\n\n\n<p>like_test=# SET max_parallel_workers_per_gather = 0;<br>SET<br>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM like_test WHERE field LIKE &#8216;post%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br>Seq Scan on public.like_test&nbsp; (cost=0.00..20834.01 rows=100 width=33) (actual time=45.528..45.530 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Filter: (like_test.field ~~ &#8216;post%&#8217;::text)<br>&nbsp; Rows Removed by Filter: 1000000<br>Query Identifier: 2553294308349252460<br>Planning Time: 0.161 ms<br>Execution Time: 45.641 ms<br>(7 rows)<br><br><br><\/p>\n\n\n\n<p>El plan de ejecuci\u00f3n usa un Seq Scan, es decir, un escaneo de toda la tabla porque no tiene otra opci\u00f3n, no hay ning\u00fan \u00edndice. Filtra descartando todos los que no empiecen por post%. El operador LIKE se representa internamente como \u2018~~\u2019<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"busquedaconindice\"><strong>B\u00fasqueda con \u00edndice<\/strong><\/h3>\n\n\n\n<p>Creamos un \u00edndice para el campo \u2018field\u2019. El \u00edndice que se crea por defecto es un btree, que es un \u00e1rbol en el que se guardan los elementos ordenados alfab\u00e9ticamente (seg\u00fan el collation) junto con el puntero a la p\u00e1gina y registro.<\/p>\n\n\n\n<p>like_test=# CREATE INDEX ON like_test(field);<br>CREATE INDEX<br><\/p>\n\n\n\n<p>Repetimos ahora la b\u00fasqueda<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM like_test WHERE field LIKE &#8216;post%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br>Index Only Scan using like_test_field_idx on public.like_test&nbsp; (cost=0.42..4.45 rows=100 width=33) (actual time=0.010..0.011 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Index Cond: ((like_test.field &gt;= &#8216;post&#8217;::text) AND (like_test.field &lt; &#8216;posu&#8217;::text))<br>&nbsp; Filter: (like_test.field ~~ &#8216;post%&#8217;::text)<br>&nbsp; Heap Fetches: 0<br>Query Identifier: 2553294308349252460<br>Planning Time: 0.469 ms<br>Execution Time: 0.162 ms<br>(8 rows)<br><\/p>\n\n\n\n<p>Vemos que se usa el \u00edndice: \u2018Index Only Scan\u2019. El planner traduce el filtro a una condici\u00f3n de b\u00fasqueda para el \u00edndice: field &gt;= \u2018post\u2019 AND field &lt; \u2018posu\u2019. Es decir todo lo que empiece por \u2018post\u2019 pero que no llegue a \u2018posu\u2019, ya que la \u2018u&#8217; ya seria la letra posterior a la \u2018t\u2019 del filtro &#8216;post%\u2019.<\/p>\n\n\n\n<p>Recordemos que en una base de datos siempre hemos usar \u00edndices para las b\u00fasquedas si queremos que todo funcione bien y r\u00e1pido.<\/p>\n\n\n\n<p>Hasta aqu\u00ed todo funciona seg\u00fan lo esperado.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"diferentedec\"><strong>Uso de LIKE en una base de datos con collate diferente de &#8216;C&#8217;<\/strong><\/h2>\n\n\n\n<p>Lo m\u00e1s habitual en nuestra instalaci\u00f3n de postgres es que el Encoding sea UTF8 y el Collate sea alguno de los xx_XX.utf8.<\/p>\n\n\n\n<p>El encoding es c\u00f3mo se codifican los caracteres a bytes, que en utf8 es variable de 1 a 4 bytes. Utf8 es el encoding m\u00e1s habitual y permite representar todos los idiomas.<\/p>\n\n\n\n<p>El collate es como se ordenan, para el orden alfab\u00e9tico espa\u00f1ol ser\u00eda el es_ES.utf8. En este orden por ejemplo la \u2018\u00d1\u2019, va despues de la \u2018N\u2019 que en un colation \u2018C\u2019 es la \u2018O\u2019 la que va despu\u00e9s de la \u2018N\u2019.<\/p>\n\n\n\n<p>Vamos a crear una base de datos est\u00e1ndar para una configuraci\u00f3n espa\u00f1ola.<\/p>\n\n\n\n<p>postgres=# CREATE DATABASE like_test;<br>CREATE DATABASE<br>postgres=# \\l like_test<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; List of databases<br>&nbsp; Name&nbsp; &nbsp; |&nbsp; Owner &nbsp; | Encoding | &nbsp; Collate &nbsp; |&nbsp; &nbsp; Ctype&nbsp; &nbsp; | Access privileges<br>&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br>like_test | postgres | UTF8 &nbsp; &nbsp; | es_ES.UTF-8 | es_ES.UTF-8 |<br>(1 row)<br><\/p>\n\n\n\n<p>Vemos que se ha creado con encoding utf8, con collate es_ES (espa\u00f1ol de Espa\u00f1a) y Ctype de es_ES tambi\u00e9n.<\/p>\n\n\n\n<p>Vamos a crear una tabla con un campo de tipo texto.<\/p>\n\n\n\n<p>Vamos a insertar un mill\u00f3n de registros con la funci\u00f3n md5, para que me genere unos caracteres pseudoaleatorios.<\/p>\n\n\n\n<p>Insertamos tambi\u00e9n un registro con el valor \u2018postgres\u2019 para nuestras pruebas.<\/p>\n\n\n\n<p>like_test=# CREATE TABLE test(field text);<br>CREATE TABLE<br>like_test=# INSERT INTO test SELECT md5(a::text) FROM generate_series(1, 1000000) AS a;<br>INSERT 0 1000000<br>like_test=# INSERT INTO test VALUES(&#8216;postgres&#8217;);<br>INSERT 0 1<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-vamos-a-crear-un-indice-btree-y-buscar-por-la-expresion-like-post\">Vamos a crear un \u00edndice btree y buscar por la expresi\u00f3n LIKE \u2018post%\u2019<\/h3>\n\n\n\n<p>like_test=# CREATE INDEX ON test(field);<br>CREATE INDEX<br>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field LIKE &#8216;post%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br>Seq Scan on public.test&nbsp; (cost=0.00..20834.01 rows=100 width=33) (actual time=43.287..43.288 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Filter: (test.field ~~ &#8216;post%&#8217;::text)<br>&nbsp; Rows Removed by Filter: 1000000<br>Query Identifier: -7947623456012888148<br>Planning Time: 0.088 ms<br>Execution Time: 43.378 ms<br><\/p>\n\n\n\n<p>\u00a1Sorpresa! No usa el \u00edndice.<\/p>\n\n\n\n<p>Vamos a probar con otro operador como un mayor o igual que &#8216;&gt;=&#8217;<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field &gt;= &#8216;post&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br>Index Only Scan using test_field_idx on public.test&nbsp; (cost=0.42..4.44 rows=1 width=33) (actual time=0.014..0.015 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Index Cond: (test.field &gt;= &#8216;post&#8217;::text)<br>&nbsp; Heap Fetches: 0<br>Query Identifier: 2811854291613324436<br>Planning Time: 0.231 ms<br>Execution Time: 0.162 ms<br>(7 rows)<br><br><\/p>\n\n\n\n<p>El \u00edndice si que se usa cuando uso el operador &#8216;&gt;=&#8217;.<\/p>\n\n\n\n<p>El problema viene porque la ordenaci\u00f3n de texto con collation es_ES.utf8 o cualquier otra diferente de \u201cC\u201d no sirve para el algoritmo que se usa para poder usar el operador LIKE, LIKE necesita un orden a nivel bytes, que cuando se usa el collation \u201cC\u201d ya se tiene.<\/p>\n\n\n\n<p>Para poder usar LIKE con un collation como es_ES.utf8 necesitamos crear un \u00edndice con un \u2018operator class\u2019 apropiado. En el caso de el tipo de dato text, el operator class que sirve para el operador LIKE se llama text_pattern_ops y no es el que se usa por defecto al crear el \u00edndice.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-para-crear-un-indice-que-sirva-para-like-con-un-collation-como-es-es-utf8-se-hace-de-esta-forma\">Para crear un \u00edndice que sirva para LIKE con un collation como es_ES.utf8 se hace de esta forma:<\/h3>\n\n\n\n<p>like_test=# CREATE INDEX ON test(field text_pattern_ops);<br>CREATE INDEX<\/p>\n\n\n\n<p>Este \u00edndice ahora si que sirve para el operador LIKE. Veamos c\u00f3mo se ejecuta ahora la consulta:<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field LIKE &#8216;post%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br>Index Only Scan using test_field_idx on public.test&nbsp; (cost=0.42..8.45 rows=100 width=33) (actual time=0.037..0.038 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Index Cond: ((test.field ~&gt;=~ &#8216;post&#8217;::text) AND (test.field ~&lt;~ &#8216;posu&#8217;::text))<br>&nbsp; Filter: (test.field ~~ &#8216;post%&#8217;::text)<br>&nbsp; Heap Fetches: 1<br>Query Identifier: -938154872528654769<br>Planning Time: 0.266 ms<br>Execution Time: 0.156 ms<br>(8 rows)<br><br><\/p>\n\n\n\n<p>Vemos que si se usa ahora el \u00edndice, y que los operadores internos que se usan para la condici\u00f3n del \u00edndice son ahora \u2018~&gt;=~\u2019 y \u2018~&lt;~\u2019 que sirven para trabajar con b\u00fasqueda de patrones.<\/p>\n\n\n\n<p>Sin embargo si queremos usar tambi\u00e9n los operadores &gt;, &gt;=, &lt;, &lt;= tendremos que crear un \u00edndice con el operator class por defecto, porque con el operator class text_pattern_ops, estos operadores de \u00edndice no funcionaran.<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field &gt;= &#8216;post&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br>Seq Scan on public.test&nbsp; (cost=0.00..20834.01 rows=100 width=33) (actual time=61.178..61.179 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Filter: (test.field &gt;= &#8216;post&#8217;::text)<br>&nbsp; Rows Removed by Filter: 1000000<br>Query Identifier: 7103150511126075852<br>Planning Time: 0.080 ms<br>Execution Time: 61.230 ms<br>(7 rows)<br><br>like_test=# CREATE INDEX ON test(field);<br>CREATE INDEX<br>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field &gt;= &#8216;post&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br>Index Only Scan using test_field_idx1 on public.test&nbsp; (cost=0.42..8.44 rows=1 width=33) (actual time=0.010..0.011 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Index Cond: (test.field &gt;= &#8216;post&#8217;::text)<br>&nbsp; Heap Fetches: 1<br>Query Identifier: 7103150511126075852<br>Planning Time: 0.307 ms<br>Execution Time: 0.147 ms<br>(7 rows)<br><\/p>\n\n\n\n<p>Vemos que despu\u00e9s de crear el \u00edndice, este si que se usa cuando usamos el operador \u2018&gt;=\u2019.<\/p>\n\n\n\n<p>Si necesitamos usar el operador LIKE y los operadores relacionales &gt;, &gt;=, &lt;, &lt;= , para un collation distinto de &#8216;C&#8217;, necesitamos crear dos \u00edndices para el mismo campo.<\/p>\n\n\n\n<p>Estos ser\u00edan los dos \u00edndices creados:<\/p>\n\n\n\n<p>like_test=# \\d test<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Table \u00abpublic.test\u00bb<br>Column | Type | Collation | Nullable | Default<br>&#8212;&#8212;&#8211;+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;<br>field&nbsp; | text | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br>Indexes:<br>&nbsp; &nbsp; \u00abtest_field_idx\u00bb btree (field text_pattern_ops)<br>&nbsp; &nbsp; \u00abtest_field_idx1\u00bb btree (field)<br><br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"palabralike\"><strong>Uso de LIKE para buscar un patr\u00f3n que contenga una palabra. LIKE &#8216;%postgres%<\/strong><\/h2>\n\n\n\n<p>Si hacemos una b\u00fasqueda con LIKE \u2018%postgres%\u2019 en nuestra base de datos de prueba anterior, veamos qu\u00e9 ocurre<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field LIKE &#8216;%post%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br>Seq Scan on public.test&nbsp; (cost=0.00..20834.01 rows=100 width=33) (actual time=66.233..66.234 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Filter: (test.field ~~ &#8216;%post%&#8217;::text)<br>&nbsp; Rows Removed by Filter: 1000000<br>Query Identifier: -938154872528654769<br>Planning Time: 0.125 ms<br>Execution Time: 66.319 ms<br>(7 rows)<br><\/p>\n\n\n\n<p>No se usa el \u00edndice, porque el orden alfab\u00e9tico del \u00edndice no sirve para encontrar algo que no sabemos por qu\u00e9 secuencia de caracteres empieza.<\/p>\n\n\n\n<p>En este caso postgres hace un Seq Scan y elimina todos los registros que no cumplen con la condici\u00f3n, algo que es tremendamente lento y nada deseable en una base de datos que funcione en producci\u00f3n.<\/p>\n\n\n\n<p>Sin embargo hay una soluci\u00f3n muy inteligente para poder usar \u00edndices con este tipo de consultas, vamos a explicarlo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"trigramas\">Trigramas: Qu\u00e9 son y c\u00f3mo funcionan<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-ejemplo-practico-uso-de-trigramas-en-postgresql\">Ejemplo pr\u00e1ctico: Uso de trigramas en PostgreSQL<\/h3>\n\n\n\n<p>Un trigrama es simplemente un grupo de tres caracteres consecutivos dentro de un texto.<\/p>\n\n\n\n<p>Por ejemplo la palabra postgres tiene estos trigramas<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>pos<\/li>\n\n\n\n<li>ost<\/li>\n\n\n\n<li>stg<\/li>\n\n\n\n<li>tgr<\/li>\n\n\n\n<li>gre<\/li>\n\n\n\n<li>res<\/li>\n<\/ul>\n\n\n\n<p>Un trigrama sirve por ejemplo para hacer b\u00fasquedas fuzzy,<\/p>\n\n\n\n<p>Si en lugar de escribir postgres correctamente lo deletreamos mal y escribimos \u201cpostgress\u201d<\/p>\n\n\n\n<p>Los trigramas que dar\u00eda son:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>pos<\/li>\n\n\n\n<li>ost<\/li>\n\n\n\n<li>stg<\/li>\n\n\n\n<li>tgr<\/li>\n\n\n\n<li>gre<\/li>\n\n\n\n<li>res<\/li>\n\n\n\n<li>ess<\/li>\n<\/ul>\n\n\n\n<p>Tiene un trigrama m\u00e1s que postgress, pero es coincidente en 6 trigramas de 7.<\/p>\n\n\n\n<p>En las b\u00fasquedas fuzzy podr\u00eda encontrar postgress.<\/p>\n\n\n\n<p>Para usar trigramas en postgres hay que crear la extensi\u00f3n primero.<\/p>\n\n\n\n<p>like_test=# CREATE EXTENSION pg_trgm ;<br>CREATE EXTENSION<\/p>\n\n\n\n<p>Vamos a ver c\u00f3mo se descompone realmente una palabra en trigramas.<\/p>\n\n\n\n<p>like_test=# SELECT show_trgm(&#8216;postgres&#8217;);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; show_trgm&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br>{\u00bb&nbsp; p\u00bb,\u00bb po\u00bb,\u00bbes \u00ab,gre,ost,pos,res,stg,tgr}<br><\/p>\n\n\n\n<p>Vemos m\u00e1s trigramas que los que hab\u00edamos descompuesto antes.<\/p>\n\n\n\n<p>Para el inicio de palabra se crean trigramas con dos espacios y con un espacio<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u201c p\u201d<\/li>\n\n\n\n<li>\u201c po\u201d<\/li>\n<\/ul>\n\n\n\n<p>Para el final de palabra se crea un trigrama adicional con un espacio al final<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u201ces \u201c<\/li>\n<\/ul>\n\n\n\n<p>Veamos ahora una operaci\u00f3n simple, ver el grado de similitud entre postgress y postgres usando trigramas.<\/p>\n\n\n\n<p>like_test=# SELECT show_trgm(&#8216;postgres&#8217;), show_trgm(&#8216;postgress&#8217;), similarity(&#8216;postgres&#8217;, &#8216;postgress&#8217;) \\gx<br>-[ RECORD 1 ]&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br>show_trgm&nbsp; | {\u00bb&nbsp; p\u00bb,\u00bb po\u00bb,\u00bbes \u00ab,gre,ost,pos,res,stg,tgr}<br>show_trgm&nbsp; | {\u00bb&nbsp; p\u00bb,\u00bb po\u00bb,ess,gre,ost,pos,res,\u00bbss \u00ab,stg,tgr}<br>similarity | 0.72727275<br><\/p>\n\n\n\n<p>Seg\u00fan como se calcula la similaridad, son un 0,72% similares. La f\u00f3rmula exacta de similitud no es importante ahora, no es de lo que queremos tratar.<\/p>\n\n\n\n<p>Lo que queremos ver es c\u00f3mo se usan los trigramas para que la b\u00fasqueda LIKE \u2018%postgres%\u2019 use un \u00edndice.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"indicesgin\"><strong>\u00cdndices GIN: Mejorando b\u00fasquedas con patrones complejos<\/strong><\/h3>\n\n\n\n<p>Introducimos ahora otro concepto, el \u00edndice GIN.<\/p>\n\n\n\n<p>Un \u00edndice GIN es un \u00edndice para localizar palabras en documentos.<\/p>\n\n\n\n<p>Tengamos estos dos documentos<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Documento<\/strong><\/th><th><strong>Texto<\/strong><\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>El perro de casa<\/td><\/tr><tr><td>2<\/td><td>La casa de arriba<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Veamos c\u00f3mo se localizan esas palabras en los documentos<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Palabra<\/strong><\/th><th><strong>Documentos<\/strong><\/th><\/tr><\/thead><tbody><tr><td>El<\/td><td>1<\/td><\/tr><tr><td>perro<\/td><td>1<\/td><\/tr><tr><td>de<\/td><td>1, 2<\/td><\/tr><tr><td>casa<\/td><td>1, 2<\/td><\/tr><tr><td>La<\/td><td>2<\/td><\/tr><tr><td>arriba<\/td><td>2<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Este tipo de \u00edndices se llaman invertidos, porque van de las palabras a los documentos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-como-funcionan-los-indices-gin-con-trigramas\">C\u00f3mo funcionan los \u00edndices GIN con trigramas<\/h3>\n\n\n\n<p>Un \u00edndice GIN para un tipo de dato texto toma los trigramas y dice en qu\u00e9 registro est\u00e1n.<\/p>\n\n\n\n<p>Si tenemos dos registros<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>id<\/strong><\/th><th><strong>field<\/strong><\/th><th><strong>trigrama simplificado resultante<\/strong><\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>uno<\/td><td>uno<\/td><\/tr><tr><td>2<\/td><td>unos<\/td><td>uno, nos<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>El \u00edndice de trigramas apuntando a los registros ser\u00eda as\u00ed:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Trigrama<\/strong><\/th><th><strong>Registro o documento en el que aparece<\/strong><\/th><\/tr><\/thead><tbody><tr><td>uno<\/td><td>1, 2<\/td><\/tr><tr><td>nos<\/td><td>2<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>C\u00f3mo se usa esto con una b\u00fasqueda LIKE \u2018%unos%\u2019<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Descomponemos \u2018unos\u2019 en trigramas simplificados para este ejemplo, y tenemos: uno, nos<\/li>\n\n\n\n<li>Buscamos en el \u00edndice los trigramas uno y nos.<\/li>\n\n\n\n<li>Vemos que \u201cuno\u201d est\u00e1 en el registro 1 y 2<\/li>\n\n\n\n<li>Vemos que \u201cdos\u201d est\u00e1 en el registro 2<\/li>\n\n\n\n<li>Vemos que la coincidencia de los dos trigramas solo est\u00e1 en el registro 2<\/li>\n\n\n\n<li>Encontramos el registro 2, solo el 2 porque el registro 1 no tiene los dos trigramas<\/li>\n\n\n\n<li>Hacemos una segunda comprobaci\u00f3n para ver que la palabra \u201cunos\u201d es la que est\u00e1 en el registro y la devolvemos.<\/li>\n<\/ul>\n\n\n\n<p>Veamos como funciona esto con un ejemplo de verdad.<\/p>\n\n\n\n<p>Vamos a crear un \u00edndice GIN con gin_trgm_ops, es decir con el operator class para trigramas, para la tabla de ejemplo que ten\u00edamos.<\/p>\n\n\n\n<p>like_test=# CREATE INDEX ON test USING GIN (field gin_trgm_ops);<br>CREATE INDEX<br>like_test=# \\d test<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Table \u00abpublic.test\u00bb<br>Column | Type | Collation | Nullable | Default<br>&#8212;&#8212;&#8211;+&#8212;&#8212;+&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;<br>field&nbsp; | text | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br>Indexes:<br>&nbsp; &nbsp; \u00abtest_field_idx\u00bb btree (field text_pattern_ops)<br>&nbsp; &nbsp; \u00abtest_field_idx1\u00bb btree (field)<br>&nbsp; &nbsp; \u00abtest_field_idx2\u00bb gin (field gin_trgm_ops)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-veamos-ahora-la-busqueda-con-un-explain\">Veamos ahora la b\u00fasqueda con un explain:<\/h2>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field LIKE &#8216;%ostgre%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br>Bitmap Heap Scan on public.test&nbsp; (cost=96.78..465.16 rows=100 width=33) (actual time=0.030..0.031 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Recheck Cond: (test.field ~~ &#8216;%ostgre%&#8217;::text)<br>&nbsp; Heap Blocks: exact=1<br>&nbsp; -&gt;&nbsp; Bitmap Index Scan on test_field_idx2&nbsp; (cost=0.00..96.75 rows=100 width=0) (actual time=0.021..0.021 rows=1 loops=1)<br>&nbsp; &nbsp; &nbsp; &nbsp; Index Cond: (test.field ~~ &#8216;%ostgre%&#8217;::text)<br>Query Identifier: -938154872528654769<br>Planning Time: 0.255 ms<br>Execution Time: 0.228 ms<br>(9 rows)<br><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vemos que el nodo m\u00e1s interno usa un Bitmap index Scan, este lo que hace es buscar en el \u00edndice los trigramas en los que se descompone la palabra &#8216;ostgre&#8217;, que son: ost, stg, gre.<\/li>\n\n\n\n<li>Comprueba que los 3 est\u00e9n presentes a la vez en el registro.<\/li>\n\n\n\n<li>Luego en el Bitmap Heap Scan, Es decir en la tabla, vuelve a recheckear la palabra \u2018ostgre\u2019 y c\u00f3mo ve que coincide, muestra el registro.<\/li>\n<\/ul>\n\n\n\n<p>Podemos incluso buscar patrones m\u00e1s complejos como \u2018_ost_res%\u2019 y vemos que tambi\u00e9n funciona<\/p>\n\n\n\n<p>like_test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test WHERE field LIKE &#8216;_ost_res%&#8217;;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; QUERY PLAN &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br>Bitmap Heap Scan on public.test&nbsp; (cost=48.78..417.16 rows=100 width=33) (actual time=0.026..0.028 rows=1 loops=1)<br>&nbsp; Output: field<br>&nbsp; Recheck Cond: (test.field ~~ &#8216;_ost_res%&#8217;::text)<br>&nbsp; Heap Blocks: exact=1<br>&nbsp; -&gt;&nbsp; Bitmap Index Scan on test_field_idx2&nbsp; (cost=0.00..48.75 rows=100 width=0) (actual time=0.016..0.017 rows=1 loops=1)<br>&nbsp; &nbsp; &nbsp; &nbsp; Index Cond: (test.field ~~ &#8216;_ost_res%&#8217;::text)<br>Query Identifier: -938154872528654769<br>Planning Time: 0.108 ms<br>Execution Time: 0.157 ms<br>(9 rows)<br><\/p>\n\n\n\n<p>Los \u00edndices GIN son muy voluminosos, crean varias entradas por documento, en el caso de trigramas, todos los trigramas en los que se descompone, que pueden ser varias decenas.<\/p>\n\n\n\n<p>Tambi\u00e9n cada inserci\u00f3n es muy costosa, cada registro tendr\u00e1 varios trigramas. Por ello normalmente las inserciones se hacen en diferido, haciendo que cada x inserciones se dispare una actualizaci\u00f3n.<\/p>\n\n\n\n<p>El uso de \u00edndices GIN con trigramas solo se recomienda para tablas que no tengan muchas actualizaciones por estas razones.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"resumen\">Resumen: Optimizaci\u00f3n de b\u00fasquedas con LIKE e \u00edndices<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Hemos visto el uso de indices Btree para el operador LIKE.<\/li>\n\n\n\n<li>Hemos visto que con collation diferente de &#8216;C&#8217; hemos de usar el operator class text_pattern_ops para poder usar el operador LIKE.<\/li>\n\n\n\n<li>Hemos visto que para usar \u00edndices con operadores relacionales para texto tenemos que crear \u00edndices Btree, con las opciones por defecto.<\/li>\n\n\n\n<li>Hemos entendido que es un trigrama.<\/li>\n\n\n\n<li>Hemos visto c\u00f3mo se puede usar trigramas con \u00edndices GIN con gin_trgm_ops para buscar patrones complejos.<\/li>\n\n\n\n<li>Hemos visto que los \u00edndices GIN son grandes porque tienen muchas entradas por cada registro y que las inserciones se demoran para agruparlas y hacerlas menos costosas.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En este art\u00edculo vamos a hablar de la b\u00fasqueda de texto con patrones con el operador LIKE.<\/p>\n<p>En concreto explicaremos c\u00f3mo mejorar su desempe\u00f1o con el uso de \u00edndices, \u00edndices Btree.<\/p>\n<p>Hablaremos de qu\u00e9 ocurre cuando usamos un collation diferente de \u201cC\u201d.<\/p>\n<p>Hablaremos de c\u00f3mo extender sus capacidades con el uso de trigramas e \u00edndices GIN, para que sea incluso capaz de usar \u00edndices con patrones tipo \u2018%postgres%\u2019.<\/p>\n<p>Al final del art\u00edculo seremos capaces de hacer b\u00fasquedas por cualquier tipo de dato texto con la eficiencia y velocidad que proporcionan los \u00edndices usando patrones de b\u00fasqueda muy complejos.<\/p>","protected":false},"author":3,"featured_media":14879,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[284],"tags":[408,406,407,177],"class_list":["post-14873","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-postgresql","tag-indices-en-bases-de-datos","tag-operador-like","tag-optimizacion-de-busquedas","tag-postgresql"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.4 (Yoast SEO v27.5) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Optimizaci\u00f3n de b\u00fasquedas con el operador LIKE en PostgreSQL<\/title>\n<meta name=\"description\" content=\"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/hopla.tech\/en\/optimizacion-busquedas-operador-like-postgresql\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"We LIKE &#039;%postgres%&#039;\" \/>\n<meta property=\"og:description\" content=\"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/hopla.tech\/en\/optimizacion-busquedas-operador-like-postgresql\/\" \/>\n<meta property=\"og:site_name\" content=\"Hopla! Tech\" \/>\n<meta property=\"article:published_time\" content=\"2025-07-14T14:48:12+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-22T14:55:25+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"675\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Hopla!\u00a0Tech\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@HoplaSoftware\" \/>\n<meta name=\"twitter:site\" content=\"@HoplaSoftware\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Hopla!\u00a0Tech\" \/>\n\t<meta name=\"twitter:label2\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/\"},\"author\":{\"name\":\"Hopla!\u00a0Tech\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/#\\\/schema\\\/person\\\/9cf81e385f7a2f68085449a2e16e1d22\"},\"headline\":\"We LIKE &#8216;%postgres%&#8217;\",\"datePublished\":\"2025-07-14T14:48:12+00:00\",\"dateModified\":\"2025-10-22T14:55:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/\"},\"wordCount\":3693,\"publisher\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/busqueda-de-texto-con-patrones-con-el-operador-like.webp\",\"keywords\":[\"\u00edndices en bases de datos\",\"operador LIKE\",\"optimizaci\u00f3n de b\u00fasquedas\",\"postgresql\"],\"articleSection\":[\"Postgresql\"],\"inLanguage\":\"en-GB\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/\",\"url\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/\",\"name\":\"Optimizaci\u00f3n de b\u00fasquedas con el operador LIKE en PostgreSQL\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/busqueda-de-texto-con-patrones-con-el-operador-like.webp\",\"datePublished\":\"2025-07-14T14:48:12+00:00\",\"dateModified\":\"2025-10-22T14:55:25+00:00\",\"description\":\"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#primaryimage\",\"url\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/busqueda-de-texto-con-patrones-con-el-operador-like.webp\",\"contentUrl\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/busqueda-de-texto-con-patrones-con-el-operador-like.webp\",\"width\":1200,\"height\":675,\"caption\":\"B\u00fasqueda texto patrones operador LIKE\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/optimizacion-busquedas-operador-like-postgresql\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\\\/\\\/hopla.tech\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"We LIKE &#8216;%postgres%&#8217;\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/#website\",\"url\":\"https:\\\/\\\/hopla.tech\\\/\",\"name\":\"Hopla! Tech\",\"description\":\"The enterprise software and consultancy company\",\"publisher\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/#organization\"},\"alternateName\":\"Hopla!\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/hopla.tech\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/#organization\",\"name\":\"Hopla! Tech\",\"alternateName\":\"Hopla!\",\"url\":\"https:\\\/\\\/hopla.tech\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/12\\\/Hopla-by-BTW-b-3.webp\",\"contentUrl\":\"https:\\\/\\\/hopla.tech\\\/wp-content\\\/uploads\\\/2025\\\/12\\\/Hopla-by-BTW-b-3.webp\",\"width\":274,\"height\":115,\"caption\":\"Hopla! Tech\"},\"image\":{\"@id\":\"https:\\\/\\\/hopla.tech\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/HoplaSoftware\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/hopla-software\\\/\",\"https:\\\/\\\/www.youtube.com\\\/@hoplasoftware7485\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/hopla.tech\\\/#\\\/schema\\\/person\\\/9cf81e385f7a2f68085449a2e16e1d22\",\"name\":\"Hopla!\u00a0Tech\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g\",\"caption\":\"Hopla!\u00a0Tech\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Optimizaci\u00f3n de b\u00fasquedas con el operador LIKE en PostgreSQL","description":"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/hopla.tech\/en\/optimizacion-busquedas-operador-like-postgresql\/","og_locale":"en_GB","og_type":"article","og_title":"We LIKE '%postgres%'","og_description":"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!","og_url":"https:\/\/hopla.tech\/en\/optimizacion-busquedas-operador-like-postgresql\/","og_site_name":"Hopla! Tech","article_published_time":"2025-07-14T14:48:12+00:00","article_modified_time":"2025-10-22T14:55:25+00:00","og_image":[{"width":1200,"height":675,"url":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp","type":"image\/webp"}],"author":"Hopla!\u00a0Tech","twitter_card":"summary_large_image","twitter_creator":"@HoplaSoftware","twitter_site":"@HoplaSoftware","twitter_misc":{"Written by":"Hopla!\u00a0Tech","Estimated reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#article","isPartOf":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/"},"author":{"name":"Hopla!\u00a0Tech","@id":"https:\/\/hopla.tech\/#\/schema\/person\/9cf81e385f7a2f68085449a2e16e1d22"},"headline":"We LIKE &#8216;%postgres%&#8217;","datePublished":"2025-07-14T14:48:12+00:00","dateModified":"2025-10-22T14:55:25+00:00","mainEntityOfPage":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/"},"wordCount":3693,"publisher":{"@id":"https:\/\/hopla.tech\/#organization"},"image":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#primaryimage"},"thumbnailUrl":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp","keywords":["\u00edndices en bases de datos","operador LIKE","optimizaci\u00f3n de b\u00fasquedas","postgresql"],"articleSection":["Postgresql"],"inLanguage":"en-GB"},{"@type":"WebPage","@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/","url":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/","name":"Optimizaci\u00f3n de b\u00fasquedas con el operador LIKE en PostgreSQL","isPartOf":{"@id":"https:\/\/hopla.tech\/#website"},"primaryImageOfPage":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#primaryimage"},"image":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#primaryimage"},"thumbnailUrl":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp","datePublished":"2025-07-14T14:48:12+00:00","dateModified":"2025-10-22T14:55:25+00:00","description":"Mejora el rendimiento de tus b\u00fasquedas en PostgreSQL con el operador LIKE y optimizaci\u00f3n de \u00edndices. \u00a1Aumenta la eficiencia ahora!","breadcrumb":{"@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/"]}]},{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#primaryimage","url":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp","contentUrl":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/07\/busqueda-de-texto-con-patrones-con-el-operador-like.webp","width":1200,"height":675,"caption":"B\u00fasqueda texto patrones operador LIKE"},{"@type":"BreadcrumbList","@id":"https:\/\/hopla.tech\/optimizacion-busquedas-operador-like-postgresql\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/hopla.tech\/"},{"@type":"ListItem","position":2,"name":"We LIKE &#8216;%postgres%&#8217;"}]},{"@type":"WebSite","@id":"https:\/\/hopla.tech\/#website","url":"https:\/\/hopla.tech\/","name":"Hopla! Tech","description":"The enterprise software and consultancy company","publisher":{"@id":"https:\/\/hopla.tech\/#organization"},"alternateName":"Hopla!","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/hopla.tech\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Organization","@id":"https:\/\/hopla.tech\/#organization","name":"Hopla! Tech","alternateName":"Hopla!","url":"https:\/\/hopla.tech\/","logo":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/hopla.tech\/#\/schema\/logo\/image\/","url":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/12\/Hopla-by-BTW-b-3.webp","contentUrl":"https:\/\/hopla.tech\/wp-content\/uploads\/2025\/12\/Hopla-by-BTW-b-3.webp","width":274,"height":115,"caption":"Hopla! Tech"},"image":{"@id":"https:\/\/hopla.tech\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/HoplaSoftware","https:\/\/www.linkedin.com\/company\/hopla-software\/","https:\/\/www.youtube.com\/@hoplasoftware7485"]},{"@type":"Person","@id":"https:\/\/hopla.tech\/#\/schema\/person\/9cf81e385f7a2f68085449a2e16e1d22","name":"Hopla!\u00a0Tech","image":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/secure.gravatar.com\/avatar\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/74a9053adf21148080273a18b879a3392f96e141e511a337179a4438f6bde966?s=96&d=mm&r=g","caption":"Hopla!\u00a0Tech"}}]}},"_links":{"self":[{"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/posts\/14873","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/comments?post=14873"}],"version-history":[{"count":8,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/posts\/14873\/revisions"}],"predecessor-version":[{"id":14895,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/posts\/14873\/revisions\/14895"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/media\/14879"}],"wp:attachment":[{"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/media?parent=14873"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/categories?post=14873"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hopla.tech\/en\/wp-json\/wp\/v2\/tags?post=14873"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}