Al cerrar el módulo 3 quedaron pendientes dos mitades: afinar el filtrado y aprender a agregar. Esta lección abre la primera. Hasta ahora, cuando has filtrado texto lo has hecho con =, es decir, exigiendo una coincidencia exacta, carácter a carácter. Eso sirve para pais = 'Portugal', donde el valor es un código cerrado, pero no sirve para nada de lo que un negocio pregunta de verdad: "los productos que llevan la palabra ecológico", "los clientes con correo portugués", "los cargos que empiezan por Responsable".

Para eso existe LIKE: un operador que compara una cadena contra un patrón en lugar de contra un valor. En esta lección aprenderás sus dos comodines, la trampa de las mayúsculas y sus tres soluciones, cómo buscar un % que sea un porcentaje de verdad y no un comodín, las expresiones regulares de PostgreSQL para los casos que LIKE no cubre, y —muy importante— por qué una búsqueda que empieza por comodín puede tumbar el rendimiento de una tabla grande.

Contenido

  1. De la igualdad exacta al patrón
  2. LIKE y NOT LIKE: sintaxis y comodines
  3. Tabla de patrones: qué casa y qué no
  4. Ejemplos sobre TiendaVerde
  5. Mayúsculas y minúsculas: ILIKE, LOWER y colaciones
  6. Buscar un % o un _ literales: ESCAPE
  7. SIMILAR TO y las expresiones regulares
  8. Rendimiento: por qué '%texto%' no puede usar un índice
  9. Errores Comunes y Consejos
  10. Ejercicios
  11. Conclusión

  1. De la igualdad exacta al patrón

El problema con = es que no admite matices. Si la dirección pide "el listado de productos ecológicos", WHERE nombre = 'ecológico' devuelve cero filas: ningún producto se llama exactamente así, la palabra está dentro del nombre.

SELECT id, nombre
FROM productos
WHERE nombre = 'ecológico';
(0 filas)

LIKE resuelve esto comparando contra un patrón, donde ciertos caracteres dejan de representarse a sí mismos y pasan a significar "cualquier cosa".

SELECT id,
       nombre,
       categoria_id,
       precio
FROM productos
WHERE nombre LIKE '%ecológic%'
ORDER BY id;
id nombre categoria_id precio
2 Arroz integral ecológico 1 kg 1 3.90
5 Tomate triturado ecológico 400 g 1 1.95
10 Detergente ecológico concentrado 1 L 3 11.20
14 Infusión de manzanilla ecológica 20 uds 4 3.25

4 filas. Fíjate en el detalle del patrón: no hemos escrito '%ecológico%' sino '%ecológic%', truncando la terminación. Así casan tanto ecológico (masculino) como ecológica (femenino, producto 14). Recortar el patrón antes de la parte variable de la palabra es un truco elemental y eficaz.

  1. LIKE y NOT LIKE: sintaxis y comodines

La forma general es:

<expresión de texto> [NOT] LIKE '<patrón>' [ESCAPE '<carácter>']

El patrón es una cadena normal en la que dos caracteres tienen significado especial:

Comodín Significado Analogía
% Cualquier secuencia de cero o más caracteres El * de los ficheros del sistema operativo
_ Exactamente un carácter, sea el que sea El ? de los ficheros

Cualquier otro carácter del patrón se compara literalmente. Y hay una consecuencia que sorprende a mucha gente: un patrón sin comodines equivale a =.

-- Estas dos condiciones son equivalentes
WHERE pais LIKE 'Portugal'
WHERE pais =    'Portugal'

El resultado de LIKE es un booleano, así que puede combinarse con AND, OR, NOT y paréntesis exactamente igual que las comparaciones de 02-03. NOT LIKE es la negación:

SELECT id,
       nombre,
       apellidos,
       puesto
FROM empleados
WHERE puesto NOT LIKE 'Responsable de%'
ORDER BY id;
id nombre apellidos puesto
1 Rosa Alcázar Vives Directora general
4 Óscar Peris Blasco Comercial
5 Laia Puig Sanchis Comercial
6 Marc Estévez Roig Atención al cliente
7 Irene Salvador Mira Operaria de almacén
8 Daniel Vercher Lluch Analista de datos

6 filas de 8. Quedan fuera Andrés Company Talens (Responsable de ventas) y Beatriz Nadal Ripoll (Responsable de logística).

Aviso de nulos, ya: NOT LIKE arrastra el mismo problema que <> en 02-03. Si la columna admite NULL, esas filas no aparecen ni con LIKE ni con NOT LIKE, porque la comparación devuelve desconocido en ambos casos. Aquí puesto es NOT NULL y no hay riesgo, pero con clientes.ciudad sí lo habría. La lección 04-03 lo explica a fondo.

  1. Tabla de patrones: qué casa y qué no

Los cuatro patrones canónicos, con el nombre que reciben en la práctica:

Patrón Nombre Casa con No casa con
'Aceite%' Prefijo (empieza por) Aceite de oliva…, Aceite corporal… Bálsamo…, aceite… (minúscula)
'%ml' Sufijo (acaba en) …extra 500 ml …20 uds, …1 kg
'%aloe%' Contiene Crema facial de aloe vera 50 ml Champú sólido de romero 80 g
'____' Longitud exacta (4 caracteres) Rosa, Marc, Laia Óscar (5), Ana (3)

Y los que mezclan ambos comodines:

Patrón Casa con Comentario
'B%a' Barcelona (en clientes.ciudad) Empieza por B y acaba en a; no casa Bosch Ferrer
'%(pack _)' …luffa (pack 3), …algodón (pack 5), …soja (pack 2) El _ sustituye al dígito
'A_a' Ana Tres letras, primera A, última a

Comprobemos el tercero de la primera tabla con datos reales:

SELECT id, nombre, precio
FROM productos
WHERE nombre LIKE '%(pack _)'
ORDER BY id;
id nombre precio
11 Estropajo vegetal de luffa (pack 3) 5.50
12 Bolsas reutilizables de algodón (pack 5) 9.90
13 Velas de cera de soja (pack 2) 13.75

3 filas, los tres productos que se venden en formato multipack. El _ ha casado con 3, 5 y 2 respectivamente. Si hubiéramos escrito '%(pack %)' habríamos obtenido lo mismo, pero también casaría (pack 12) o (pack familiar): _ es más estricto y por tanto más preciso cuando sabes que ahí va un solo carácter.

El comodín _ es fácil de olvidar

Un caso instructivo con pedidos.metodo_pago:

SELECT DISTINCT metodo_pago
FROM pedidos
WHERE metodo_pago LIKE 'contra_eembolso';
metodo_pago
contrareembolso

Casa. Y no porque el valor tenga un guion bajo —no lo tiene—, sino porque el _ del patrón ha casado con la letra r de contra**r**eembolso. Es el error que hace que WHERE columna LIKE 'precio_unitario' encuentre cosas que no esperabas cuando buscas nombres de columna en un catálogo de metadatos. En la sección 6 verás cómo evitarlo.

  1. Ejemplos sobre TiendaVerde

4.1. Productos por formato: la trampa del sufijo

El catálogo codifica el formato al final del nombre. Los envases líquidos pequeños acaban en ml:

SELECT id,
       nombre,
       categoria_id,
       precio
FROM productos
WHERE nombre LIKE '%ml'
ORDER BY id;
id nombre categoria_id precio
1 Aceite de oliva virgen extra 500 ml 1 12.50
6 Crema facial de aloe vera 50 ml 2 18.90
8 Aceite corporal de almendras 200 ml 2 14.25
9 Bálsamo labial de caléndula 15 ml 2 4.60
16 Kombucha de jengibre 750 ml 4 4.95

5 filas. Ahora los que se venden por peso en gramos:

-- ⚠️ INCORRECTA: '%g' también casa con 'kg'
SELECT id, nombre
FROM productos
WHERE nombre LIKE '%g'
ORDER BY id;
id nombre
2 Arroz integral ecológico 1 kg
3 Miel de azahar cruda 500 g
4 Pasta de espelta 500 g
5 Tomate triturado ecológico 400 g
7 Champú sólido de romero 80 g
15 Té verde matcha ceremonial 30 g
19 Desodorante natural en barra 50 g

7 filas, y una de ellas está de más: el arroz se vende en kilos, no en gramos. '%g' significa "acaba en la letra g", y kg acaba en g. La corrección es incluir el espacio en el patrón:

-- ✅ CORRECTA
SELECT id, nombre, precio
FROM productos
WHERE nombre LIKE '% g'
ORDER BY id;
id nombre precio
3 Miel de azahar cruda 500 g 9.75
4 Pasta de espelta 500 g 2.80
5 Tomate triturado ecológico 400 g 1.95
7 Champú sólido de romero 80 g 8.40
15 Té verde matcha ceremonial 30 g 22.00
19 Desodorante natural en barra 50 g 7.80

6 filas. El espacio delante de la g es la diferencia entre un informe correcto y otro que mezcla unidades. Es exactamente el tipo de error que no da ningún aviso.

4.2. Clientes por dominio de correo

TiendaVerde vende a tres países y los correos de ejemplo reflejan el dominio de cada uno. Los clientes portugueses:

SELECT id,
       nombre || ' ' || apellidos AS cliente,
       email,
       ciudad,
       pais
FROM clientes
WHERE email LIKE '%.pt'
ORDER BY id;
id cliente email ciudad pais
7 Sofia Moreira Costa [email protected] Lisboa Portugal
8 Tiago Almeida Nunes [email protected] Oporto Portugal

Y los franceses, combinando dos patrones con OR:

SELECT id,
       nombre || ' ' || apellidos AS cliente,
       email,
       pais
FROM clientes
WHERE email LIKE '%.fr'
   OR email LIKE '%.pt'
ORDER BY pais, id;
id cliente email pais
9 Camille Dubois [email protected] Francia
10 Julien Moreau [email protected] Francia
7 Sofia Moreira Costa [email protected] Portugal
8 Tiago Almeida Nunes [email protected] Portugal

4 filas: los cuatro clientes internacionales. Los otros 11 usan example.com.

Ojo con lo que estás midiendo. Filtrar por el dominio del correo no es lo mismo que filtrar por pais. Aquí coinciden porque el conjunto de datos está construido así, pero en una base real hay portugueses con @gmail.com y españoles con @example.fr. Si la pregunta de negocio es "clientes de Portugal", la respuesta correcta es WHERE pais = 'Portugal', no un LIKE sobre el correo. LIKE es potente y por eso es fácil usarlo para responder a una pregunta parecida pero distinta.

4.3. Cargos del organigrama

SELECT id,
       nombre || ' ' || apellidos AS empleado,
       puesto,
       salario
FROM empleados
WHERE puesto LIKE 'Responsable de%'
ORDER BY id;
id empleado puesto salario
2 Andrés Company Talens Responsable de ventas 41000.00
3 Beatriz Nadal Ripoll Responsable de logística 39500.00

2 filas: los dos mandos intermedios de la jerarquía que viste en 03-06. Este patrón —prefijo fijo, resto libre— es el más frecuente en la práctica y, como verás en la sección 8, el único que puede aprovechar un índice.

  1. Mayúsculas y minúsculas: ILIKE, LOWER y colaciones

En 02-03 viste que pais = 'españa' devuelve cero filas. LIKE hereda exactamente el mismo comportamiento: en PostgreSQL, LIKE distingue mayúsculas de minúsculas.

SELECT id, nombre FROM productos WHERE nombre LIKE 'aceite%';
(0 filas)
SELECT id, nombre FROM productos WHERE nombre LIKE 'Aceite%';
id nombre
1 Aceite de oliva virgen extra 500 ml
8 Aceite corporal de almendras 200 ml

Y esto es un problema real, porque quien escribe en un buscador no pulsa mayúsculas. Hay tres soluciones.

Solución 1: ILIKE (extensión de PostgreSQL)

SELECT id, nombre, precio
FROM productos
WHERE nombre ILIKE '%aceite%'
ORDER BY id;
id nombre precio
1 Aceite de oliva virgen extra 500 ml 12.50
8 Aceite corporal de almendras 200 ml 14.25

La I es de insensitive. Su negación es NOT ILIKE. Es la opción más legible y la que usaremos en el curso cuando trabajemos sobre PostgreSQL, con una advertencia: no es SQL estándar, así que una consulta con ILIKE no es portable.

Solución 2: LOWER en los dos lados

SELECT id, nombre, precio
FROM productos
WHERE LOWER(nombre) LIKE LOWER('%Aceite%')
ORDER BY id;

Devuelve las mismas 2 filas. Es portable a cualquier motor, y por eso es la forma que verás en código que debe funcionar en varias bases de datos.

Dos detalles importantes:

  • LOWER va en los dos lados. Si escribes LOWER(nombre) LIKE '%Aceite%' no casará nunca nada, porque la columna se ha pasado a minúsculas pero el patrón no.
  • Aplicar una función a la columna impide usar un índice normal. Es el mismo aviso de 02-03 sobre WHERE precio - coste > 5. La solución (un índice sobre la expresión LOWER(nombre)) es materia del módulo 8.

LOWER y UPPER se estudian a fondo, junto con el resto de funciones de cadena, en la lección 06-01. Aquí solo las usamos como herramienta.

Solución 3: una colación insensible

PostgreSQL 12 y posteriores permiten definir colaciones no deterministas, que hacen que la propia comparación ignore mayúsculas (e incluso tildes). Se declaran una vez y afectan a =, a LIKE y a ORDER BY sin tocar las consultas:

CREATE COLLATION es_ci (
    provider = icu,
    locale   = 'es-ES-u-ks-level2',
    deterministic = false
);

Es la solución más limpia cuando toda una columna debe compararse así, pero es una decisión de diseño de esquema (módulo 5) y tiene contrapartidas: con una colación no determinista, LIKE sobre esa columna no puede usar los índices habituales.

Comportamiento por motor

Motor ¿LIKE distingue mayúsculas? Forma insensible idiomática
PostgreSQL , siempre ILIKE, o LOWER(col) LIKE LOWER(pat), o colación no determinista
MySQL / MariaDB No por defecto: depende de la colación, y la habitual (utf8mb4_0900_ai_ci) es insensible a mayúsculas y a tildes Ya lo es; para hacerlo sensible, LIKE ... COLLATE utf8mb4_0900_as_cs o BINARY
SQLite No para ASCII, para el resto: 'a' LIKE 'A' es cierto, pero 'á' LIKE 'Á' es falso LOWER(col) LIKE LOWER(pat), con el mismo límite ASCII
SQL Server Depende de la colación de la columna; las instalaciones típicas son insensibles (_CI_) LOWER(...) o COLLATE ..._CI_AS explícito
Oracle LOWER(...), o los parámetros de sesión NLS_COMP/NLS_SORT

Consecuencia práctica: una consulta con LIKE que funciona perfectamente en MySQL puede devolver cero filas al portarla a PostgreSQL, sin que nada haya cambiado en los datos. Es una de las diferencias de dialecto que más tiempo hace perder en migraciones. Cuando escribas SQL que deba viajar, usa LOWER() en ambos lados y no dependas del comportamiento por defecto de nadie.

  1. Buscar un % o un _ literales: ESCAPE

¿Y si lo que buscas es un signo de porcentaje? Como % significa "cualquier cosa", el patrón '%%%' no busca un porcentaje: casa con absolutamente todo.

La solución es escapar el comodín, marcándolo con un carácter que le quite su poder. En PostgreSQL (y en MySQL) el carácter de escape por defecto es la barra invertida \:

SELECT texto
FROM (VALUES ('Rebaja del 20% en bebidas'),
             ('Envío gratis a partir de 50 EUR'),
             ('Pack 2x1 en cosmética')) AS t(texto)
WHERE texto LIKE '%\%%';
texto
Rebaja del 20% en bebidas

Lee el patrón '%\%%' de izquierda a derecha: % (cualquier cosa) + \% (un signo de porcentaje literal) + % (cualquier cosa).

Si la barra invertida te resulta ilegible —o si tus datos contienen barras invertidas—, la cláusula ESCAPE permite elegir otro carácter:

SELECT texto
FROM (VALUES ('Rebaja del 20% en bebidas'),
             ('Envío gratis a partir de 50 EUR'),
             ('Pack 2x1 en cosmética')) AS t(texto)
WHERE texto LIKE '%!%%' ESCAPE '!';
texto
Rebaja del 20% en bebidas

Mismo resultado, patrón más legible. ESCAPE funciona igual para el guion bajo. Recuperando el caso de la sección 3, así se busca un _ de verdad:

Consulta Qué busca realmente
LIKE 'contra_eembolso' contra + cualquier carácter + eembolso → casa con contrareembolso
LIKE 'contra\_eembolso' contra_eembolso literal → 0 filas en TiendaVerde
LIKE 'precio\_unitario' El nombre de columna exacto, con su guion bajo

Nota de dialecto: el estándar SQL no define un carácter de escape por defecto; obliga a declararlo con ESCAPE. PostgreSQL y MySQL sí tienen uno (\); SQL Server y Oracle no, así que allí LIKE '%\%%' busca literalmente una barra seguida de cualquier cosa y hay que escribir ESCAPE '\' explícitamente. Escribir siempre ESCAPE explícito es la opción portable.

  1. SIMILAR TO y las expresiones regulares

LIKE se queda corto en cuanto la pregunta incluye alternativas ("acaba en ml, g, kg, L o uds"), repeticiones ("dos o más dígitos") o clases de caracteres ("una letra seguida de un número"). PostgreSQL ofrece dos familias más.

7.1. SIMILAR TO

Es SQL estándar y es un híbrido: usa los comodines de LIKE (% y _) más algunos operadores de expresión regular (|, *, +, ?, (), [], {}).

SELECT id, nombre
FROM productos
WHERE nombre SIMILAR TO '%(ml|kg)'
ORDER BY id;
id nombre
1 Aceite de oliva virgen extra 500 ml
2 Arroz integral ecológico 1 kg
6 Crema facial de aloe vera 50 ml
8 Aceite corporal de almendras 200 ml
9 Bálsamo labial de caléndula 15 ml
16 Kombucha de jengibre 750 ml

6 filas: los 5 productos en mililitros más el arroz en kilos. Con LIKE habrían hecho falta dos condiciones unidas por OR.

En la práctica SIMILAR TO se usa poco: quien necesita esa potencia suele preferir las expresiones regulares completas, y quien no la necesita se queda con LIKE. Conviene conocerlo porque aparece en código heredado y porque es la única de las tres familias que forma parte del estándar junto con LIKE.

7.2. Expresiones regulares POSIX: ~, ~*, !~, !~*

Son los operadores nativos de PostgreSQL y usan la sintaxis de expresiones regulares que ya conoces de cualquier lenguaje de programación:

Operador Significado
~ Casa la expresión regular, distinguiendo mayúsculas
~* Casa, sin distinguir mayúsculas
!~ No casa, distinguiendo mayúsculas
!~* No casa, sin distinguir mayúsculas

Diferencia fundamental con LIKE: una expresión regular casa por defecto en cualquier parte de la cadena, no de principio a fin. Por eso ^ (inicio) y $ (fin) son necesarios cuando quieres anclar.

Ejemplo 1: validar el formato de un correo electrónico.

SELECT id,
       nombre || ' ' || apellidos AS cliente,
       email
FROM clientes
WHERE email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
ORDER BY id;
(0 filas)

Cero filas, que aquí es la respuesta deseada: los 15 correos de TiendaVerde tienen formato válido. Este patrón —buscar lo que no cumple— es la forma habitual de auditar la calidad de los datos antes de una migración o de una campaña de correo.

Ejemplo 2: productos cuyo nombre termina en una unidad de medida.

SELECT id,
       nombre,
       precio
FROM productos
WHERE nombre !~ '[0-9]+ ?(ml|kg|g|L|uds)$'
ORDER BY id;
id nombre precio
11 Estropajo vegetal de luffa (pack 3) 5.50
12 Bolsas reutilizables de algodón (pack 5) 9.90
13 Velas de cera de soja (pack 2) 13.75
18 Cepillo de dientes de bambú 3.50

4 filas: los únicos cuatro productos del catálogo que no indican gramaje al final. Los otros 16 sí. Esto es una comprobación de calidad de catálogo real: si mañana el equipo de contenidos añade una referencia sin formato, esta consulta la delata. Con LIKE haría falta encadenar cinco condiciones con OR y aun así no podrías exigir que delante de la unidad hubiera un número.

7.3. Las tres familias comparadas

LIKE / ILIKE SIMILAR TO ~ (POSIX)
Estándar SQL No (PostgreSQL)
Comodines %, _ %, _ + operadores regex Sintaxis regex pura (., *, +, ?, [], (), |)
Anclado Siempre a la cadena completa Siempre a la cadena completa No, salvo que uses ^ y $
Alternativas (a o b) No Sí, (a|b) Sí, a|b
Repeticiones No Sí, {2,4} Sí, {2,4}
Insensible a mayúsculas ILIKE No directamente ~*
Puede usar índice B-tree Sí, si el patrón empieza por texto fijo No Solo con anclaje ^ y análisis del planificador
Legibilidad Muy alta Media Baja para quien no domina regex
Cuándo usarlo El 90 % de los casos Casi nunca Validaciones y extracciones complejas

Nota de dialecto: las expresiones regulares varían mucho entre motores. MySQL usa REGEXP / RLIKE (y REGEXP_LIKE desde 8.0), Oracle usa REGEXP_LIKE, y SQLite tiene el operador REGEXP pero la función que lo implementa no viene incluida: hay que registrarla desde la aplicación. SIMILAR TO prácticamente solo existe en PostgreSQL. Si tu SQL debe ser portable, LIKE es la única apuesta segura.

  1. Rendimiento: por qué '%texto%' no puede usar un índice

Este es el punto que separa una consulta que funciona con 20 filas de una que funciona con 20 millones.

Un índice B-tree —el índice normal, el que estudiarás en el módulo 8— guarda los valores ordenados alfabéticamente, como un diccionario. Y eso determina qué puede y qué no puede acelerar:

flowchart TD
    A["LIKE 'Aceite%'"] --> B["El índice está ordenado.<br/>Todo lo que empieza por 'Aceite'<br/>está junto, en un tramo contiguo"]
    B --> C["✅ Salta directamente al tramo.<br/>Índice aprovechado"]
    D["LIKE '%aceite%'"] --> E["Lo que contiene 'aceite' en medio<br/>puede estar en cualquier punto<br/>del orden alfabético"]
    E --> F["❌ Hay que leer y comprobar<br/>TODAS las filas: escaneo completo"]

Piénsalo con un diccionario en papel: buscar todas las palabras que empiezan por aceite es abrir por la A y leer un tramo. Buscar todas las que contienen aceite obliga a leer el diccionario entero.

Patrón Tipo ¿Índice B-tree?
LIKE 'Aceite%' Prefijo fijo
LIKE 'Aceite de%oliva%' Prefijo fijo + resto , usa el prefijo para acotar
LIKE '%aceite' Sufijo No
LIKE '%aceite%' Contiene No
LIKE '_ceite%' Empieza por comodín No
ILIKE 'Aceite%' Insensible No con un índice normal
LOWER(nombre) LIKE 'aceite%' Función sobre la columna No con un índice normal, con un índice sobre la expresión

Hay dos matices que conviene conocer desde ya:

  1. Para que LIKE 'prefijo%' use el índice en PostgreSQL, el índice debe usar una clase de operadores especial (text_pattern_ops) salvo que la base esté en la colación C. Es un detalle que se explica en el módulo 8, pero explica por qué a veces "tengo el índice y no lo usa".
  2. Para las búsquedas '%texto%' existe una solución específica: la extensión pg_trgm, que descompone el texto en trigramas (grupos de tres caracteres) y permite crear índices GIN o GiST capaces de acelerar LIKE '%texto%' e ILIKE '%texto%'. Se activa con CREATE EXTENSION pg_trgm; y también se ve en el módulo 8.

Y cuándo LIKE deja de ser la herramienta

LIKE compara caracteres, no palabras. No sabe que ecológico y ecológicos son la misma palabra, no ignora las tildes, no ordena los resultados por relevancia y no entiende que quien busca "aceite oliva" quiere las mismas filas que quien busca "oliva aceite".

Para eso existe la búsqueda de texto completo (full-text search), que PostgreSQL incorpora de serie con los tipos tsvector y tsquery y el operador @@. Convierte el texto en una lista de raíces léxicas, ignora las palabras vacías y admite índices GIN muy eficientes. No entra en este curso, pero conviene que sepas que existe: si te descubres encadenando cinco ILIKE '%…%' con OR para hacer un buscador, la herramienta correcta ya no es LIKE.

Errores Comunes y Consejos

  • Usar = cuando querías LIKE. WHERE nombre = '%ml' no da error: busca literalmente la cadena %ml y devuelve cero filas.
  • Olvidar que LIKE distingue mayúsculas en PostgreSQL. Cero filas y ninguna pista. Si vienes de MySQL, es la primera sorpresa que te llevarás.
  • Aplicar LOWER solo a un lado. LOWER(nombre) LIKE '%Aceite%' no casa nunca. Van los dos.
  • Escribir '%g' cuando querías '% g'. El arroz de 1 kg se cuela en el listado de productos por gramos. Ancla el patrón con el separador que corresponda.
  • Confiar en que _ es un guion bajo literal. Es un comodín. Para buscar el carácter, \_ o ESCAPE.
  • Buscar un % sin escaparlo. LIKE '%%%' devuelve todas las filas de la tabla.
  • Suponer que NOT LIKE devuelve "todo lo demás". Las filas con NULL no salen ni en un lado ni en el otro. Comprueba que las dos mitades sumen el total (04-03).
  • Usar LIKE sobre el correo para deducir el país. Responde a una pregunta parecida, no a la misma. Usa la columna que modela el dato.
  • Poner un comodín al principio en una tabla grande. '%texto%' fuerza un escaneo completo. Con 20 filas no lo notas; con 20 millones, tu consulta tarda minutos.
  • Consejo: empieza siempre por el patrón más laxo y ve ajustando. Lanza ILIKE '%aceit%', mira qué sale, y solo entonces afina. Es más rápido que adivinar el patrón exacto a la primera.
  • Consejo: cuenta las filas de la condición y de su negación. Si LIKE da 6 y NOT LIKE da 14 sobre una tabla de 20, la lógica cierra. Si no cierra, hay nulos.
  • Consejo: si el patrón tiene que ser portable, escribe LOWER(col) LIKE LOWER(pat) ESCAPE '\'. Es más verboso, pero funciona igual en los cinco motores.

Ejercicios

Ejercicio 1

El equipo de contenidos quiere revisar cómo está escrito el catálogo. Escribe tres consultas sobre productos:

  1. Los productos cuyo nombre contiene la palabra natural (en cualquier posición y sin importar mayúsculas).
  2. Los productos cuyo nombre empieza por la letra C.
  3. Los productos que no llevan la palabra ecológic en el nombre pero pertenecen a la categoría 1 (Alimentación).

Indica en cada caso cuántas filas salen y por qué has elegido ese patrón.

Ejercicio 2

Marketing prepara una campaña por correo electrónico y necesita segmentar. Sobre clientes:

  1. Cuenta cuántos clientes tienen un correo del dominio example.com usando LIKE.
  2. Escribe la consulta que devuelve los clientes cuyo nombre de pila tiene exactamente 3 letras.
  3. Un compañero propone WHERE email LIKE '%@example.com%' para el primer apartado. ¿Devuelve lo mismo? ¿Es equivalente? ¿Cuál preferirías y por qué?

Ejercicio 3

Dirección quiere una auditoría del catálogo. Escribe una única consulta que devuelva, para cada producto, su id, su nombre y una columna formato que valga:

  • el propio nombre si no acaba en unidad de medida (los cuatro que viste en la sección 7.2),
  • y nada más: solo esos cuatro productos deben aparecer.

Resuélvelo primero con expresiones regulares y después responde: ¿podrías haberlo hecho solo con LIKE? ¿Qué te faltaría?

Soluciones

Solución 1

1. Contiene natural, sin importar mayúsculas:

SELECT id, nombre, categoria_id, precio
FROM productos
WHERE nombre ILIKE '%natural%'
ORDER BY id;
id nombre categoria_id precio
19 Desodorante natural en barra 50 g 5 7.80

1 fila. Se usa ILIKE porque no sabemos si la palabra aparece en mayúscula al principio de algún nombre; con LIKE '%natural%' el resultado sería el mismo en estos datos, pero la consulta sería frágil ante una futura referencia llamada "Natural…".

2. Empieza por C:

SELECT id, nombre, precio
FROM productos
WHERE nombre LIKE 'C%'
ORDER BY id;
id nombre precio
6 Crema facial de aloe vera 50 ml 18.90
7 Champú sólido de romero 80 g 8.40
18 Cepillo de dientes de bambú 3.50
20 Cápsulas de espirulina 120 uds 16.40

4 filas. Aquí sí conviene LIKE y no ILIKE: los nombres de producto empiezan por mayúscula por convención, y ILIKE 'c%' no aportaría nada mientras que sí impediría aprovechar un índice.

3. Alimentación sin la palabra ecológic:

SELECT id, nombre, categoria_id, precio
FROM productos
WHERE categoria_id = 1
  AND nombre NOT LIKE '%ecológic%'
ORDER BY id;
id nombre categoria_id precio
1 Aceite de oliva virgen extra 500 ml 1 12.50
3 Miel de azahar cruda 500 g 1 9.75
4 Pasta de espelta 500 g 1 2.80

3 filas. La comprobación de coherencia: la categoría 1 tiene 5 productos, dos de ellos (2 y 5) llevan ecológico en el nombre, y 5 − 2 = 3. Cierra. Que cierre es importante justamente porque nombre es NOT NULL; si admitiera nulos, NOT LIKE los habría descartado y la cuenta no cuadraría.

Solución 2

1.

SELECT COUNT(*) AS clientes_dominio_com
FROM clientes
WHERE email LIKE '%@example.com';
clientes_dominio_com
11

11 clientes, los 15 menos los 2 portugueses y los 2 franceses.

2. Nombre de pila de exactamente 3 letras: tres guiones bajos y nada más.

SELECT id, nombre, apellidos, ciudad
FROM clientes
WHERE nombre LIKE '___'
ORDER BY id;
id nombre apellidos ciudad
5 Ana Belmonte Roca Barcelona
6 Pau Llorens Vidal Valencia

2 filas. Con LIKE '___' (tres _) exiges exactamente tres caracteres, porque el patrón se ancla a la cadena completa. Si hubieras escrito '___%' obtendrías todos los nombres de tres letras o más, es decir, casi toda la tabla.

3. '%@example.com%' devuelve lo mismo aquí (11 filas), pero no es equivalente. El % final significa "y después cualquier cosa", así que también casaría con [email protected] o con [email protected]. Es un patrón más laxo que responde a "el correo contiene @example.com", no a "el correo termina en @example.com".

Preferible '%@example.com': dice exactamente lo que queremos, no arrastra falsos positivos y —detalle nada menor— un patrón que acaba sin comodín sigue sin poder usar índice, pero al menos no invita a errores lógicos. La regla general: no pongas comodines que no necesites, cada uno amplía silenciosamente el conjunto de respuestas.

Solución 3

SELECT id,
       nombre AS formato
FROM productos
WHERE nombre !~ '[0-9]+ ?(ml|kg|g|L|uds)$'
ORDER BY id;
id formato
11 Estropajo vegetal de luffa (pack 3)
12 Bolsas reutilizables de algodón (pack 5)
13 Velas de cera de soja (pack 2)
18 Cepillo de dientes de bambú

4 filas. Desglose del patrón:

Fragmento Significado
[0-9]+ Uno o más dígitos
? Un espacio opcional
(ml|kg|g|L|uds) Cualquiera de las cinco unidades
$ Anclado al final de la cadena
!~ Devuelve las filas que no casan

¿Se podría con LIKE? Solo a medias. Podrías escribir:

-- Aproximación con LIKE: incompleta
WHERE nombre NOT LIKE '%ml'
  AND nombre NOT LIKE '% g'
  AND nombre NOT LIKE '% kg'
  AND nombre NOT LIKE '% L'
  AND nombre NOT LIKE '% uds'

y en estos datos daría las mismas 4 filas. Pero te faltan tres cosas que LIKE no sabe expresar:

  1. Exigir que delante de la unidad haya un número. Un producto llamado "Jabón de manos ml" —una errata perfectamente posible— quedaría clasificado como "tiene formato", porque LIKE '%ml' solo mira las dos últimas letras. La expresión regular exige [0-9]+ delante y lo detectaría.
  2. Tolerar variaciones de mayúsculas. "Gel de ducha 500 mL" no casa con '%ml' en PostgreSQL; con regex basta cambiar ~ por ~*.
  3. Mantenerlo. Cinco condiciones encadenadas con AND crecen a diez en cuanto el catálogo añada cl y oz; la expresión regular solo añade dos alternativas dentro del paréntesis.

Ese es el criterio de elección: LIKE mientras la condición sea una sola forma fija; regex cuando aparezcan alternativas, repeticiones o clases de caracteres.

Conclusión

Ya sabes buscar por patrones y, sobre todo, sabes cuándo no debes:

  • LIKE compara contra un patrón, no contra un valor, y devuelve un booleano combinable con AND, OR y NOT. NOT LIKE es su negación, con la misma ceguera ante los NULL que <>.
  • Los dos comodines son % (cero o más caracteres) y _ (exactamente uno). Un patrón sin comodines equivale a =, y el patrón se ancla siempre a la cadena completa.
  • En PostgreSQL LIKE distingue mayúsculas. Las tres salidas son ILIKE (cómodo pero no estándar), LOWER(col) LIKE LOWER(pat) (portable) y una colación no determinista (decisión de esquema). En MySQL el comportamiento por defecto es el contrario y en SQLite solo aplica a ASCII.
  • Para buscar un % o un _ literales hay que escaparlos: \% por defecto en PostgreSQL, o el carácter que declares con ESCAPE, que es la forma portable.
  • Cuando la condición incluye alternativas, repeticiones o clases de caracteres, LIKE se queda corto: entran SIMILAR TO (estándar, poco usado) y sobre todo las expresiones regulares ~, ~*, !~, !~* de PostgreSQL, con las que has validado los 15 correos y detectado los 4 productos sin gramaje.
  • El rendimiento depende del primer carácter del patrón: 'texto%' puede usar un índice B-tree; '%texto%' obliga a leer la tabla entera. Para ese caso está pg_trgm (módulo 8), y cuando lo que necesitas es un buscador de verdad, la búsqueda de texto completo.

En la lección siguiente, operadores IN y BETWEEN, seguirás afinando el filtrado pero por otro camino: en vez de patrones de texto, listas de valores y rangos. Verás cómo IN sustituye a una cadena interminable de OR, cómo BETWEEN compacta un rango incluyendo siempre ambos extremos, y te encontrarás con uno de los errores más caros de todo SQL: NOT IN con una lista que contiene un NULL no devuelve "el resto", devuelve exactamente cero filas.

Curso de SQL

Módulo 1: Introducción a SQL

Módulo 2: Consultas básicas de SQL

Módulo 3: Trabajando con múltiples tablas

Módulo 4: Filtrado avanzado de datos

Módulo 5: Manipulación de datos

Módulo 6: Funciones avanzadas de SQL

Módulo 7: Subconsultas y consultas anidadas

Módulo 8: Índices y optimización de rendimiento

Módulo 9: Transacciones y concurrencia

Módulo 10: Temas avanzados

Módulo 11: SQL en la práctica

Módulo 12: Proyecto final

© Copyright 2026. Todos los derechos reservados