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
- De la igualdad exacta al patrón
LIKEyNOT LIKE: sintaxis y comodines- Tabla de patrones: qué casa y qué no
- Ejemplos sobre TiendaVerde
- Mayúsculas y minúsculas:
ILIKE,LOWERy colaciones - Buscar un
%o un_literales:ESCAPE SIMILAR TOy las expresiones regulares- Rendimiento: por qué
'%texto%'no puede usar un índice - Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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.
LIKE resuelve esto comparando contra un patrón, donde ciertos caracteres dejan de representarse a sí mismos y pasan a significar "cualquier cosa".
| 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.
LIKE y NOT LIKE: sintaxis y comodines
LIKE y NOT LIKE: sintaxis y comodinesLa forma general es:
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 =.
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 LIKEarrastra el mismo problema que<>en 02-03. Si la columna admiteNULL, esas filas no aparecen ni conLIKEni conNOT LIKE, porque la comparación devuelve desconocido en ambos casos. AquípuestoesNOT NULLy no hay riesgo, pero conclientes.ciudadsí lo habría. La lección 04-03 lo explica a fondo.
- 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:
| 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:
| 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.
- 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:
| 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:
| 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 | 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 | 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.comy españoles con@example.fr. Si la pregunta de negocio es "clientes de Portugal", la respuesta correcta esWHERE pais = 'Portugal', no unLIKEsobre el correo.LIKEes 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.
- Mayúsculas y minúsculas:
ILIKE, LOWER y colaciones
ILIKE, LOWER y colacionesEn 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.
| 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)
| 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
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:
LOWERva en los dos lados. Si escribesLOWER(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ónLOWER(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:
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 | Sí, 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, sí 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 | Sí | LOWER(...), o los parámetros de sesión NLS_COMP/NLS_SORT |
Consecuencia práctica: una consulta con
LIKEque 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, usaLOWER()en ambos lados y no dependas del comportamiento por defecto de nadie.
- Buscar un
% o un _ literales: ESCAPE
% 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 escribirESCAPE '\'explícitamente. Escribir siempreESCAPEexplícito es la opción portable.
SIMILAR TO y las expresiones regulares
SIMILAR TO y las expresiones regularesLIKE 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 (|, *, +, ?, (), [], {}).
| 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;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.
| 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 | Sí | Sí | 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(yREGEXP_LIKEdesde 8.0), Oracle usaREGEXP_LIKE, y SQLite tiene el operadorREGEXPpero la función que lo implementa no viene incluida: hay que registrarla desde la aplicación.SIMILAR TOprácticamente solo existe en PostgreSQL. Si tu SQL debe ser portable,LIKEes la única apuesta segura.
- Rendimiento: por qué
'%texto%' no puede usar un índice
'%texto%' no puede usar un índiceEste 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 | Sí |
LIKE 'Aceite de%oliva%' |
Prefijo fijo + resto | Sí, 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, sí con un índice sobre la expresión |
Hay dos matices que conviene conocer desde ya:
- 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ónC. Es un detalle que se explica en el módulo 8, pero explica por qué a veces "tengo el índice y no lo usa". - Para las búsquedas
'%texto%'existe una solución específica: la extensiónpg_trgm, que descompone el texto en trigramas (grupos de tres caracteres) y permite crear índices GIN o GiST capaces de acelerarLIKE '%texto%'eILIKE '%texto%'. Se activa conCREATE 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íasLIKE.WHERE nombre = '%ml'no da error: busca literalmente la cadena%mly devuelve cero filas. - Olvidar que
LIKEdistingue mayúsculas en PostgreSQL. Cero filas y ninguna pista. Si vienes de MySQL, es la primera sorpresa que te llevarás. - Aplicar
LOWERsolo 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,\_oESCAPE. - Buscar un
%sin escaparlo.LIKE '%%%'devuelve todas las filas de la tabla. - Suponer que
NOT LIKEdevuelve "todo lo demás". Las filas conNULLno salen ni en un lado ni en el otro. Comprueba que las dos mitades sumen el total (04-03). - Usar
LIKEsobre 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
LIKEda 6 yNOT LIKEda 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:
- Los productos cuyo nombre contiene la palabra natural (en cualquier posición y sin importar mayúsculas).
- Los productos cuyo nombre empieza por la letra
C. - Los productos que no llevan la palabra ecológic en el nombre pero sí 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:
- Cuenta cuántos clientes tienen un correo del dominio
example.comusandoLIKE. - Escribe la consulta que devuelve los clientes cuyo nombre de pila tiene exactamente 3 letras.
- 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:
| 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:
| 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.
| 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.
| 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
| 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:
- 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. - Tolerar variaciones de mayúsculas. "Gel de ducha 500 mL" no casa con
'%ml'en PostgreSQL; con regex basta cambiar~por~*. - Mantenerlo. Cinco condiciones encadenadas con
ANDcrecen a diez en cuanto el catálogo añadaclyoz; 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:
LIKEcompara contra un patrón, no contra un valor, y devuelve un booleano combinable conAND,ORyNOT.NOT LIKEes su negación, con la misma ceguera ante losNULLque<>.- 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
LIKEdistingue mayúsculas. Las tres salidas sonILIKE(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 conESCAPE, que es la forma portable. - Cuando la condición incluye alternativas, repeticiones o clases de caracteres,
LIKEse queda corto: entranSIMILAR 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
- ¿Qué es SQL?
- Configurando tu entorno SQL
- Sintaxis básica de SQL
- Entendiendo bases de datos y tablas
- El modelo relacional: claves primarias y foráneas
- La base de datos del curso: TiendaVerde
Módulo 2: Consultas básicas de SQL
- Instrucción SELECT
- Alias, expresiones y columnas calculadas
- Filtrando datos con WHERE
- DISTINCT y eliminación de duplicados
- Ordenando datos con ORDER BY
- Limitando resultados con LIMIT
Módulo 3: Trabajando con múltiples tablas
- Operaciones JOIN
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- SELF JOIN y CROSS JOIN
- Uniones de conjuntos: UNION, INTERSECT y EXCEPT
Módulo 4: Filtrado avanzado de datos
- Usando LIKE para coincidencia de patrones
- Operadores IN y BETWEEN
- Valores NULL y IS NULL
- Funciones de agregación: COUNT, SUM, AVG, MIN y MAX
- Agregando datos con GROUP BY
- Cláusula HAVING
Módulo 5: Manipulación de datos
- Creando tablas y restricciones con CREATE TABLE
- Instrucción INSERT
- Instrucción UPDATE
- Instrucción DELETE
- Instrucción UPSERT (MERGE)
- Modificando el esquema: ALTER TABLE y migraciones seguras
Módulo 6: Funciones avanzadas de SQL
- Funciones de cadena
- Funciones numéricas
- Funciones de fecha y hora
- Conversión de tipos y manejo de NULL: CAST y COALESCE
- Expresiones condicionales
Módulo 7: Subconsultas y consultas anidadas
- Introducción a subconsultas
- Subconsultas correlacionadas
- EXISTS y NOT EXISTS
- Usando subconsultas en cláusulas SELECT, FROM y WHERE
- Subconsultas o JOIN: cuál elegir
Módulo 8: Índices y optimización de rendimiento
- Entendiendo los índices
- Creación y gestión de índices
- Tipos de índice y cuándo no indexar
- Técnicas de optimización de consultas
- Análisis del rendimiento de consultas
Módulo 9: Transacciones y concurrencia
- Introducción a las transacciones
- Propiedades ACID
- Instrucciones de control de transacciones
- Niveles de aislamiento y anomalías de concurrencia
- Manejo de concurrencia: bloqueos e interbloqueos
Módulo 10: Temas avanzados
- Vistas
- Expresiones de tabla comunes (CTE)
- Funciones de ventana
- Procedimientos almacenados
- Triggers
- JSON y datos semiestructurados
Módulo 11: SQL en la práctica
- Casos de uso en el mundo real
- Mejores prácticas
- Seguridad: inyección SQL, permisos y roles
- SQL para análisis de datos
- SQL en desarrollo web
