Tema
Filtros: LIKE, IN, BETWEEN
Con = y > puedes filtrar mucho, pero los buscadores reales necesitan más: "productos que contengan libro", "clientes de estas tres ciudades", "pedidos entre dos fechas", "productos sin categoría". En esta lección dominarás LIKE, IN, BETWEEN, IS NULL, la precedencia de AND/OR, la expresión CASE y las funciones de texto y fecha más útiles.
Concepto
Piensa en WHERE como el filtro de búsqueda de una tienda en línea: cada casilla que marcas (categoría, rango de precio, texto buscado) es una condición. Estos operadores te dejan expresar esas casillas de forma corta y legible.
LIKE: buscar por patrón
LIKE compara textos usando comodines:
| Comodín | Significa | Ejemplo | Coincide con |
|---|---|---|---|
% | Cero o más caracteres | 'Libro%' | Libro, Libro Aprende SQL |
_ | Exactamente un carácter | '_ana' | Ana, Lana (si existiera) |
sql
-- Productos cuyo nombre empieza con "T"
SELECT nombre FROM productos WHERE nombre LIKE 'T%';| nombre |
|---|
| Teclado mecánico |
| Taza térmica |
sql
-- Productos que contienen "de" en cualquier parte
SELECT nombre FROM productos WHERE nombre LIKE '%de%';| nombre |
|---|
| Libro Aprende SQL |
| Cien años de soledad |
| Lámpara de escritorio |
¿Por qué salió el libro? Porque "Aprende" contiene "de": LIKE busca caracteres, no palabras completas.
sql
-- Emails que terminan en @correo.com
SELECT email FROM clientes WHERE email LIKE '%@correo.com';¿Distingue mayúsculas?
| SGBD | LIKE | Alternativa |
|---|---|---|
| MySQL | No distingue (con collation _ci) | — |
| PostgreSQL | Sí distingue | ILIKE no distingue |
| SQLite | No distingue para ASCII | — |
Forma portable de buscar sin importar mayúsculas:
sql
SELECT nombre FROM productos WHERE LOWER(nombre) LIKE '%taza%';Error común
LIKE '%texto%' (con % al inicio) no puede usar índices normales: en tablas grandes recorre todas las filas. Para buscadores serios usa búsqueda de texto completo (PostgreSQL o FULLTEXT en MySQL).
IN: pertenece a una lista
En vez de encadenar muchos OR:
sql
-- Largo
SELECT nombre, ciudad FROM clientes
WHERE ciudad = 'Monterrey' OR ciudad = 'Puebla' OR ciudad = 'Guadalajara';
-- Corto y legible
SELECT nombre, ciudad FROM clientes
WHERE ciudad IN ('Monterrey', 'Puebla', 'Guadalajara');| nombre | ciudad |
|---|---|
| Carlos Ruiz | Guadalajara |
| María Pérez | Monterrey |
| Lucía Gómez | Puebla |
NOT IN invierte:
sql
SELECT id, estado FROM pedidos WHERE estado NOT IN ('entregado', 'cancelado');| id | estado |
|---|---|
| 3 | enviado |
| 4 | pendiente |
IN también acepta una subconsulta (lo verás en Subconsultas y CTEs):
sql
-- Clientes que tienen al menos un pedido
SELECT nombre FROM clientes
WHERE id IN (SELECT cliente_id FROM pedidos);NOT IN con NULL
Si la lista o subconsulta de un NOT IN contiene un NULL, el resultado es vacío. Ejemplo: WHERE id NOT IN (SELECT categoria_id FROM productos) no devuelve nada porque el Cable USB-C tiene categoria_id NULL. Usa NOT EXISTS o filtra los nulos con WHERE categoria_id IS NOT NULL.
BETWEEN: dentro de un rango
BETWEEN a AND b incluye ambos extremos:
sql
SELECT nombre, precio FROM productos
WHERE precio BETWEEN 300 AND 1000
ORDER BY precio;| nombre | precio |
|---|---|
| Libro Aprende SQL | 349.00 |
| Lámpara de escritorio | 459.00 |
| Audífonos Bluetooth | 899.00 |
Con fechas:
sql
SELECT id, fecha FROM pedidos
WHERE fecha BETWEEN '2024-05-01' AND '2024-05-31';| id | fecha |
|---|---|
| 1 | 2024-05-02 |
| 2 | 2024-05-10 |
BETWEEN con fecha y hora
Si la columna es TIMESTAMP, BETWEEN '2024-05-01' AND '2024-05-31' excluye casi todo el día 31 (lo toma como 31 a las 00:00:00). Para fechas con hora usa un rango semiabierto: WHERE creado_en >= '2024-05-01' AND creado_en < '2024-06-01'
IS NULL e IS NOT NULL
sql
-- Productos sin categoría
SELECT nombre FROM productos WHERE categoria_id IS NULL;| nombre |
|---|
| Cable USB-C |
sql
-- Productos con categoría
SELECT COUNT(*) FROM productos WHERE categoria_id IS NOT NULL; -- 7Precedencia: AND antes que OR
AND se evalúa antes que OR, igual que la multiplicación antes que la suma. Mira este error típico:
sql
-- Intención: productos de Libros o Hogar que cuesten menos de 300
SELECT nombre, categoria_id, precio FROM productos
WHERE categoria_id = 2 OR categoria_id = 3 AND precio < 300;| nombre | categoria_id | precio |
|---|---|---|
| Libro Aprende SQL | 2 | 349.00 |
| Cien años de soledad | 2 | 289.00 |
| Taza térmica | 3 | 199.00 |
¡Salió un libro de 349! La BD lo leyó como categoria_id = 2 OR (categoria_id = 3 AND precio < 300). Con paréntesis:
sql
SELECT nombre, categoria_id, precio FROM productos
WHERE (categoria_id = 2 OR categoria_id = 3) AND precio < 300;
-- o mejor: WHERE categoria_id IN (2, 3) AND precio < 300;| nombre | categoria_id | precio |
|---|---|---|
| Cien años de soledad | 2 | 289.00 |
| Taza térmica | 3 | 199.00 |
Buena práctica
Siempre que mezcles AND y OR, usa paréntesis aunque no hagan falta. Tu yo del futuro te lo agradecerá.
CASE: condiciones dentro del SELECT
CASE es el "if" de SQL. Devuelve un valor según condiciones:
sql
SELECT
nombre,
stock,
CASE
WHEN stock = 0 THEN 'Agotado'
WHEN stock <= 10 THEN 'Pocas piezas'
WHEN stock <= 30 THEN 'Disponible'
ELSE 'Mucho stock'
END AS disponibilidad
FROM productos
ORDER BY stock;| nombre | stock | disponibilidad |
|---|---|---|
| Monitor 24 pulgadas | 5 | Pocas piezas |
| Teclado mecánico | 10 | Pocas piezas |
| Lámpara de escritorio | 15 | Disponible |
| Audífonos Bluetooth | 25 | Disponible |
| Cien años de soledad | 30 | Disponible |
| Libro Aprende SQL | 40 | Mucho stock |
| Taza térmica | 50 | Mucho stock |
| Cable USB-C | 100 | Mucho stock |
Las condiciones se evalúan en orden y se queda con la primera verdadera. CASE también sirve en ORDER BY para un orden personalizado:
sql
SELECT id, estado FROM pedidos
ORDER BY CASE estado
WHEN 'pendiente' THEN 1
WHEN 'enviado' THEN 2
WHEN 'entregado' THEN 3
ELSE 4
END;| id | estado |
|---|---|
| 4 | pendiente |
| 3 | enviado |
| 1 | entregado |
| 2 | entregado |
| 5 | cancelado |
Funciones de texto útiles
| Función | Ejemplo | Resultado |
|---|---|---|
UPPER(x) / LOWER(x) | UPPER('ana') | ANA |
LENGTH(x) | LENGTH('Hola') | 4 (en MySQL cuenta bytes; usa CHAR_LENGTH) |
TRIM(x) | TRIM(' hola ') | hola |
SUBSTRING(x, inicio, n) | SUBSTRING('Monterrey', 1, 3) | Mon |
REPLACE(x, a, b) | REPLACE('a-b', '-', '/') | a/b |
CONCAT(a, b) | CONCAT('Hola ', 'Ana') | Hola Ana |
sql
-- Dominio del correo de cada cliente
SELECT email, SUBSTRING(email, POSITION('@' IN email) + 1) AS dominio
FROM clientes;Funciones de fecha útiles
Aquí los motores difieren bastante:
| Necesidad | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
| Hoy | CURDATE() | CURRENT_DATE | DATE('now') |
| Año de una fecha | YEAR(fecha) | EXTRACT(YEAR FROM fecha) | strftime('%Y', fecha) |
| Mes | MONTH(fecha) | EXTRACT(MONTH FROM fecha) | strftime('%m', fecha) |
| Sumar 7 días | fecha + INTERVAL 7 DAY | fecha + INTERVAL '7 days' | DATE(fecha, '+7 days') |
| Días entre fechas | DATEDIFF(f2, f1) | f2 - f1 | julianday(f2) - julianday(f1) |
EXTRACT(YEAR FROM fecha) funciona en MySQL y PostgreSQL:
sql
-- Pedidos de junio (cualquier año)
SELECT id, fecha FROM pedidos WHERE EXTRACT(MONTH FROM fecha) = 6;| id | fecha |
|---|---|
| 3 | 2024-06-01 |
| 4 | 2024-06-15 |
| 5 | 2024-06-20 |
Rendimiento
Aplicar una función a la columna (EXTRACT(MONTH FROM fecha) = 6) impide usar un índice sobre fecha. Si buscas un mes concreto, prefiere el rango: fecha >= '2024-06-01' AND fecha < '2024-07-01'.
Caso real: el buscador de la tienda
El usuario escribe "a", elige las categorías Electrónica y Hogar y un precio entre 100 y 1,000:
sql
SELECT id, nombre, precio
FROM productos
WHERE LOWER(nombre) LIKE '%a%'
AND categoria_id IN (1, 3)
AND precio BETWEEN 100 AND 1000
AND stock > 0
ORDER BY precio;| id | nombre | precio |
|---|---|---|
| 7 | Taza térmica | 199.00 |
| 6 | Lámpara de escritorio | 459.00 |
| 1 | Audífonos Bluetooth | 899.00 |
Ejercicios
- Muestra los clientes cuyo nombre contiene "ar" (sin importar mayúsculas).
- Muestra los productos cuyo id sea 2, 4 o 6.
- Muestra los clientes registrados en febrero de 2024 usando
BETWEEN. - Lista los productos con una columna
rangoque diga'Económico'si cuesta menos de 300,'Medio'si cuesta hasta 1000 y'Premium'si cuesta más. - ¿Qué devuelve esta consulta y por qué?
SELECT nombre FROM productos WHERE categoria_id <> 1;
Ver solución
sql
-- 1
SELECT nombre FROM clientes WHERE LOWER(nombre) LIKE '%ar%';| nombre |
|---|
| Carlos Ruiz |
| María Pérez |
sql
-- 2
SELECT id, nombre FROM productos WHERE id IN (2, 4, 6);
-- 3
SELECT nombre, fecha_registro FROM clientes
WHERE fecha_registro BETWEEN '2024-02-01' AND '2024-02-29';| nombre | fecha_registro |
|---|---|
| Carlos Ruiz | 2024-02-03 |
| María Pérez | 2024-02-20 |
sql
-- 4
SELECT nombre, precio,
CASE
WHEN precio < 300 THEN 'Económico'
WHEN precio <= 1000 THEN 'Medio'
ELSE 'Premium'
END AS rango
FROM productos;| nombre | precio | rango |
|---|---|---|
| Audífonos Bluetooth | 899.00 | Medio |
| Teclado mecánico | 1250.00 | Premium |
| Monitor 24 pulgadas | 3499.00 | Premium |
| Libro Aprende SQL | 349.00 | Medio |
| Cien años de soledad | 289.00 | Económico |
| Lámpara de escritorio | 459.00 | Medio |
| Taza térmica | 199.00 | Económico |
| Cable USB-C | 99.00 | Económico |
- Devuelve los productos de Libros y Hogar (4 filas), pero no el Cable USB-C: su
categoria_ides NULL yNULL <> 1no es verdadero sino desconocido. Para incluirlo:WHERE categoria_id <> 1 OR categoria_id IS NULL.
Resumen
LIKEbusca patrones con%(cualquier cosa) y_(un carácter). En PostgreSQL existeILIKE.IN (...)reemplaza cadenas deOR; cuidado conNOT INy losNULL.BETWEEN a AND bincluye los extremos; con fechas y horas usa rangos semiabiertos.IS NULL/IS NOT NULLpara valores vacíos.ANDva antes queOR: usa paréntesis.CASE WHEN ... THEN ... ENDcrea columnas condicionales y órdenes personalizados.