En esta página
SELECT, WHERE y ORDER BY
SELECT, WHERE y ORDER BY
La instrucción SELECT es el corazón de SQL. Es la que usarás con mayor frecuencia en cualquier aplicación: para mostrar listados, buscar registros, generar reportes o alimentar componentes de tu interfaz. En esta lección dominarás todas las variantes del filtrado y ordenamiento de datos.
Preparar los datos de práctica
Antes de consultar, necesitamos datos. Vamos a crear un esquema simple de tienda para esta lección:
CREATE TABLE categorias (
id SERIAL PRIMARY KEY,
nombre VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE productos (
id SERIAL PRIMARY KEY,
nombre VARCHAR(200) NOT NULL,
descripcion TEXT,
precio NUMERIC(10,2) NOT NULL CHECK (precio >= 0),
stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
categoria_id INTEGER REFERENCES categorias(id),
activo BOOLEAN NOT NULL DEFAULT true,
creado_en TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO categorias (nombre) VALUES
('Electrónica'), ('Ropa'), ('Libros'), ('Hogar'), ('Deportes');
INSERT INTO productos (nombre, descripcion, precio, stock, categoria_id) VALUES
('Laptop Pro 15"', 'Laptop con chip M3', 1299.99, 5, 1),
('Auriculares BT', 'Auriculares Bluetooth ANC', 199.99, 15, 1),
('Teclado Mecánico', 'Switch Cherry MX Brown', 89.99, 30, 1),
('Camiseta Algodón', 'Algodón 100%, varios colores', 19.99, 200, 2),
('Jeans Slim', 'Denim premium lavado oscuro', 59.99, 80, 2),
('Clean Code', 'Robert C. Martin', 35.99, 50, 3),
('The Pragmatic Programmer', 'Hunt & Thomas 20th Ed', 42.99, 40, 3),
('Sartén Antiadherente', 'Titanio 28cm', 34.99, 25, 4),
('Pelota de Fútbol', 'Tamaño 5, cuero sintético', 24.99, 60, 5),
('Zapatillas Running', 'Amortiguación reactiva', 119.99, 35, 5),
('Mouse Inalámbrico', 'Ergonómico, 1600 DPI', 49.99, 45, 1),
('Sudadera Polar', 'Interior afelpado, unisex', 45.99, 90, 2);La instrucción SELECT básica
-- Seleccionar todas las columnas (evitar en producción)
SELECT * FROM productos;
-- Seleccionar columnas específicas (la práctica recomendada)
SELECT nombre, precio, stock FROM productos;
-- El orden de las columnas en SELECT define el orden de salida
SELECT stock, nombre, precio FROM productos;[!tip] Evita
SELECT *en código de producción. Cuando añades nuevas columnas a una tabla,SELECT *las incluye automáticamente, lo que puede romper el código que asume un número fijo de columnas. También transfiere datos innecesarios por la red. Especifica siempre las columnas que necesitas.
Aliases con AS
Los aliases renombran columnas o tablas en los resultados, sin modificar la base de datos:
-- Alias de columna
SELECT
nombre AS producto,
precio AS precio_venta,
stock AS unidades_disponibles
FROM productos;
-- Alias sin AS (también válido, pero menos legible)
SELECT nombre producto, precio precio_venta FROM productos;
-- Alias de tabla (útil en JOINs y subconsultas)
SELECT p.nombre, p.precio
FROM productos p
WHERE p.stock > 0;
-- Expresiones calculadas con alias
SELECT
nombre,
precio,
precio * 1.21 AS precio_con_iva,
stock * precio AS valor_inventario
FROM productos;Filtrar con WHERE
La cláusula WHERE filtra las filas que cumplen una condición. Solo las filas donde la condición evalúa a TRUE aparecen en el resultado.
-- Comparaciones básicas: =, <>, !=, <, >, <=, >=
SELECT nombre, precio FROM productos WHERE precio > 100;
SELECT nombre, stock FROM productos WHERE stock = 0;
SELECT nombre FROM productos WHERE categoria_id <> 1;
-- NULL requiere IS NULL o IS NOT NULL (nunca =)
SELECT nombre FROM productos WHERE descripcion IS NULL;
SELECT nombre FROM productos WHERE descripcion IS NOT NULL;AND, OR y NOT
-- AND: ambas condiciones deben ser verdaderas
SELECT nombre, precio, stock
FROM productos
WHERE precio < 50 AND stock > 20;
-- OR: al menos una condición debe ser verdadera
SELECT nombre, precio
FROM productos
WHERE categoria_id = 1 OR categoria_id = 3;
-- NOT: niega la condición
SELECT nombre, activo
FROM productos
WHERE NOT activo;
-- Combinar con paréntesis para controlar precedencia
-- AND tiene mayor precedencia que OR
SELECT nombre, precio, categoria_id
FROM productos
WHERE (categoria_id = 1 OR categoria_id = 5)
AND precio < 200;IN y NOT IN
-- IN: equivale a múltiples OR, pero más limpio
SELECT nombre, categoria_id
FROM productos
WHERE categoria_id IN (1, 3, 5);
-- NOT IN: excluir valores
SELECT nombre
FROM productos
WHERE categoria_id NOT IN (2, 4);
-- IN con lista de textos
SELECT nombre
FROM productos
WHERE nombre IN ('Clean Code', 'Mouse Inalámbrico', 'Laptop Pro 15"');BETWEEN
-- BETWEEN: rango INCLUSIVO (incluye ambos extremos)
SELECT nombre, precio
FROM productos
WHERE precio BETWEEN 20 AND 60;
-- Equivalente a:
SELECT nombre, precio
FROM productos
WHERE precio >= 20 AND precio <= 60;
-- BETWEEN con fechas
SELECT nombre, creado_en
FROM productos
WHERE creado_en BETWEEN '2024-01-01' AND '2024-12-31';
-- NOT BETWEEN
SELECT nombre, precio
FROM productos
WHERE precio NOT BETWEEN 100 AND 1000;LIKE e ILIKE
-- LIKE: búsqueda de patrones (sensible a mayúsculas)
-- % = cualquier secuencia de caracteres (incluido ninguno)
-- _ = exactamente un carácter
SELECT nombre FROM productos WHERE nombre LIKE 'L%'; -- Empieza con L
SELECT nombre FROM productos WHERE nombre LIKE '%ón%'; -- Contiene 'ón'
SELECT nombre FROM productos WHERE nombre LIKE '__________'; -- Exactamente 10 chars
SELECT nombre FROM productos WHERE nombre LIKE '%ing'; -- Termina en 'ing'
-- ILIKE: búsqueda insensible a mayúsculas/minúsculas (extensión PostgreSQL)
SELECT nombre FROM productos WHERE nombre ILIKE 'laptop%'; -- 'Laptop', 'LAPTOP', etc.
SELECT nombre FROM productos WHERE nombre ILIKE '%pro%'; -- Busca 'Pro', 'PRO', 'pro'
-- Para búsqueda de texto completo, considera usar índices de texto
-- (lo veremos en la lección de índices)ORDER BY
Sin ORDER BY, el orden de los resultados es indeterminado. Siempre que el orden importe, especifícalo:
-- Orden ascendente (ASC es el predeterminado)
SELECT nombre, precio FROM productos ORDER BY precio ASC;
SELECT nombre, precio FROM productos ORDER BY precio; -- Mismo resultado
-- Orden descendente
SELECT nombre, precio FROM productos ORDER BY precio DESC;
-- Múltiples columnas de ordenamiento
SELECT nombre, categoria_id, precio
FROM productos
ORDER BY categoria_id ASC, precio DESC;
-- Primero ordena por categoría, luego por precio dentro de cada categoría
-- Ordenar por alias
SELECT nombre, precio * 1.21 AS precio_iva
FROM productos
ORDER BY precio_iva DESC;
-- Ordenar por posición de columna (no recomendado: frágil si cambias columnas)
SELECT nombre, precio FROM productos ORDER BY 2 DESC;
-- NULL al final o al principio
SELECT nombre, descripcion
FROM productos
ORDER BY descripcion NULLS LAST; -- NULLs al final
ORDER BY descripcion NULLS FIRST; -- NULLs al principioLIMIT y OFFSET — Paginación
-- LIMIT: máximo de filas a devolver
SELECT nombre, precio FROM productos
ORDER BY precio DESC
LIMIT 5; -- Los 5 más caros
-- OFFSET: saltar N filas (para paginación)
-- Página 1: las primeras 4 filas
SELECT nombre, precio FROM productos
ORDER BY id
LIMIT 4 OFFSET 0;
-- Página 2: las siguientes 4 filas
SELECT nombre, precio FROM productos
ORDER BY id
LIMIT 4 OFFSET 4;
-- Página 3
SELECT nombre, precio FROM productos
ORDER BY id
LIMIT 4 OFFSET 8;
-- Fórmula: OFFSET = (número_página - 1) * tamaño_página[!warning] El problema de rendimiento con OFFSET grande: cuando tienes millones de filas,
OFFSET 1000000hace que PostgreSQL recorra y descarte un millón de filas antes de devolver los resultados. Para paginación eficiente en grandes tablas, usa keyset pagination (también llamada cursor pagination): guarda el último ID devuelto y úsalo en el siguienteWHERE id > ultimo_id ORDER BY id LIMIT n. Esto es siempre O(log n) gracias a los índices.
DISTINCT
-- Eliminar filas duplicadas en el resultado
SELECT DISTINCT categoria_id FROM productos ORDER BY categoria_id;
-- DISTINCT en múltiples columnas (combinación única)
SELECT DISTINCT categoria_id, activo FROM productos ORDER BY categoria_id;
-- COUNT con DISTINCT
SELECT COUNT(DISTINCT categoria_id) AS total_categorias FROM productos;Expresiones en SELECT
Puedes realizar cálculos y transformaciones directamente en el SELECT:
SELECT
nombre,
precio,
precio * 1.21 AS precio_con_iva,
ROUND(precio * 1.21, 2) AS precio_iva_redondeado,
stock * precio AS valor_total_inventario,
UPPER(nombre) AS nombre_mayusculas,
LENGTH(nombre) AS largo_nombre,
CURRENT_DATE AS fecha_consulta,
CASE
WHEN precio < 30 THEN 'económico'
WHEN precio < 100 THEN 'rango medio'
ELSE 'premium'
END AS categoria_precio
FROM productos
WHERE activo = true
ORDER BY precio DESC;Ejemplo completo: buscador de productos
Aquí un ejemplo realista de consulta que combina todo lo aprendido:
-- Búsqueda de productos con filtros múltiples y paginación
-- (Como la que haría una API de e-commerce)
SELECT
p.id,
p.nombre,
p.precio,
p.stock,
CASE WHEN p.stock > 0 THEN 'disponible' ELSE 'agotado' END AS estado_stock
FROM productos p
WHERE
p.activo = true
AND p.categoria_id IN (1, 5)
AND p.precio BETWEEN 20 AND 200
AND p.nombre ILIKE '%a%'
ORDER BY
p.precio ASC,
p.nombre ASC
LIMIT 10
OFFSET 0;[!info] El orden de evaluación en SQL NO es el orden de escritura. El motor procesa las cláusulas en este orden lógico:
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT/OFFSET. Por eso no puedes usar un alias definido enSELECTdentro de unWHERE(el alias aún no existe cuando se evalúaWHERE). Sin embargo,ORDER BYsí puede usar aliases deSELECT.
Resumen de operadores de filtrado
| Operador | Uso | Ejemplo |
|---|---|---|
= |
Igualdad exacta | precio = 99.99 |
<>, != |
Desigualdad | estado <> 'activo' |
>, <, >=, <= |
Comparación numérica/fecha | precio >= 100 |
BETWEEN x AND y |
Rango inclusivo | precio BETWEEN 10 AND 50 |
IN (...) |
Valor en lista | id IN (1, 2, 3) |
LIKE |
Patrón (case-sensitive) | nombre LIKE 'A%' |
ILIKE |
Patrón (case-insensitive) | nombre ILIKE '%pro%' |
IS NULL |
Valor nulo | descripcion IS NULL |
IS NOT NULL |
Valor no nulo | email IS NOT NULL |
AND, OR, NOT |
Lógica booleana | a = 1 AND b > 2 |
Dominar estas herramientas te permite responder prácticamente cualquier pregunta sobre los datos almacenados. En la siguiente lección aprenderemos a modificar datos: INSERT, UPDATE, DELETE y las poderosas cláusulas RETURNING y ON CONFLICT.
nextSteps:
- slug: insert-update-y-delete label: "Siguiente: INSERT, UPDATE y DELETE"
Inicia sesión para guardar tu progreso