Publicado en

SQL: consultas esenciales de referencia

Consulta SQL completa con SELECT, LEFT JOIN, WHERE, GROUP BY, HAVING, ORDER BY y LIMIT con sintaxis coloreada

Referencia rápida de los comandos SQL más usados, organizados por categoría. Todos los ejemplos usan el esquema de una tienda online: tablas usuarios, pedidos, productos y pedidos_productos.

Consultas básicas

-- Todas las columnas
SELECT * FROM productos;
-- Columnas específicas
SELECT nombre, precio FROM productos;
-- Alias de columna
SELECT nombre AS producto, precio AS coste FROM productos;
-- Alias de tabla
SELECT u.nombre, u.email FROM usuarios AS u;
-- Eliminar duplicados
SELECT DISTINCT ciudad FROM usuarios;
-- Limitar resultados
SELECT nombre, precio FROM productos LIMIT 10;
-- Paginar resultados (saltar 20, coger 10)
SELECT nombre FROM productos LIMIT 10 OFFSET 20;

Filtros con WHERE

-- Igualdad y desigualdad
SELECT * FROM usuarios WHERE nombre = 'Ana';
SELECT * FROM usuarios WHERE nombre != 'Ana';
-- Comparaciones numéricas
SELECT * FROM productos WHERE precio > 50;
SELECT * FROM productos WHERE precio >= 50 AND precio <= 200;
-- Rango (inclusivo)
SELECT * FROM productos WHERE precio BETWEEN 20 AND 100;
-- Lista de valores
SELECT * FROM usuarios WHERE ciudad IN ('Madrid', 'Barcelona', 'Valencia');
-- Patrón de texto
SELECT * FROM usuarios WHERE email LIKE '%@gmail.com';  -- termina en
SELECT * FROM usuarios WHERE nombre LIKE 'A%';          -- empieza por
SELECT * FROM usuarios WHERE nombre LIKE '_na';         -- un carácter + na
-- Valores nulos
SELECT * FROM pedidos WHERE fecha_envio IS NULL;
SELECT * FROM pedidos WHERE fecha_envio IS NOT NULL;
-- Combinación de condiciones
SELECT * FROM productos WHERE (precio < 10 OR precio > 500) AND nombre LIKE 'A%';
-- Negar condición
SELECT * FROM usuarios WHERE NOT ciudad = 'Madrid';

Ordenar y agrupar

-- Ordenar ascendente (por defecto)
SELECT nombre, precio FROM productos ORDER BY precio ASC;
-- Ordenar descendente
SELECT nombre, precio FROM productos ORDER BY precio DESC;
-- Ordenar por varias columnas
SELECT nombre, ciudad FROM usuarios ORDER BY ciudad ASC, nombre ASC;
-- Contar filas
SELECT COUNT(*) FROM pedidos;
SELECT COUNT(DISTINCT id_usuario) FROM pedidos;  -- clientes únicos
-- Funciones de agregación
SELECT
  COUNT(*)        AS total_pedidos,
  SUM(total)      AS ingresos,
  AVG(total)      AS ticket_medio,
  MIN(total)      AS pedido_min,
  MAX(total)      AS pedido_max
FROM pedidos;
-- Agrupar por columna
SELECT id_usuario, COUNT(*) AS num_pedidos
FROM pedidos
GROUP BY id_usuario;
-- Filtrar grupos (HAVING va después de GROUP BY, WHERE no aplica aquí)
SELECT id_usuario, COUNT(*) AS num_pedidos
FROM pedidos
GROUP BY id_usuario
HAVING COUNT(*) > 2;
-- Orden habitual de cláusulas
SELECT columnas
FROM tabla
WHERE condición_filas
GROUP BY columnas
HAVING condición_grupos
ORDER BY columnas
LIMIT n;

JOINs

-- INNER JOIN: solo filas con coincidencia en ambas tablas
SELECT u.nombre, p.id_pedido, p.total
FROM usuarios AS u
INNER JOIN pedidos AS p ON u.id_usuario = p.id_usuario;
-- LEFT JOIN: todos de la izquierda + coincidencias de la derecha (NULL si no hay)
SELECT u.nombre, p.id_pedido
FROM usuarios AS u
LEFT JOIN pedidos AS p ON u.id_usuario = p.id_usuario;
-- RIGHT JOIN: todos de la derecha + coincidencias de la izquierda
SELECT u.nombre, p.id_pedido
FROM usuarios AS u
RIGHT JOIN pedidos AS p ON u.id_usuario = p.id_usuario;
-- FULL OUTER JOIN: todos de ambas tablas (no disponible en MySQL, usar UNION)
SELECT u.nombre, p.id_pedido
FROM usuarios AS u
FULL OUTER JOIN pedidos AS p ON u.id_usuario = p.id_usuario;
-- Tres tablas combinadas
SELECT u.nombre, pr.nombre AS producto
FROM usuarios AS u
INNER JOIN pedidos AS p          ON u.id_usuario  = p.id_usuario
INNER JOIN pedidos_productos AS pp ON p.id_pedido = pp.id_pedido
INNER JOIN productos AS pr       ON pp.id_producto = pr.id_producto;

Subconsultas

-- Filtrar con el resultado de otra consulta
SELECT nombre, precio
FROM productos
WHERE id_producto IN (
  SELECT id_producto FROM pedidos_productos WHERE id_pedido = 5
);
-- Subconsulta con EXISTS (más eficiente que IN para grandes volúmenes)
SELECT nombre FROM usuarios AS u
WHERE EXISTS (
  SELECT 1 FROM pedidos AS p WHERE p.id_usuario = u.id_usuario
);
-- Subconsulta en SELECT (valor escalar)
SELECT
  nombre,
  precio,
  (SELECT AVG(precio) FROM productos) AS media_general
FROM productos;

Insertar datos

-- Una fila
INSERT INTO usuarios (nombre, email, ciudad)
VALUES ('Ana García', 'ana@mail.com', 'Madrid');
-- Varias filas a la vez
INSERT INTO usuarios (nombre, email, ciudad) VALUES
  ('Luis Martín', 'luis@mail.com', 'Barcelona'),
  ('María López', 'maria@mail.com', 'Valencia'),
  ('Carlos Ruiz', 'carlos@mail.com', 'Sevilla');
-- Insertar desde otra tabla
INSERT INTO clientes_vip (nombre, email)
SELECT nombre, email
FROM usuarios
WHERE total_compras > 1000;

Actualizar datos

-- Actualizar una columna
UPDATE productos SET precio = 79.99 WHERE id_producto = 1;
-- Actualizar varias columnas
UPDATE usuarios
SET ciudad = 'Valencia', email = 'nuevo@mail.com'
WHERE id_usuario = 3;
-- Actualizar con cálculo
UPDATE pedidos SET total = total * 0.9 WHERE id_usuario = 2;
-- Actualizar basándose en otra tabla
UPDATE productos AS pr
SET precio = precio * 0.95
WHERE id_producto IN (
  SELECT id_producto FROM pedidos_productos
  GROUP BY id_producto HAVING COUNT(*) > 10
);

Eliminar datos

-- Eliminar filas concretas
DELETE FROM pedidos WHERE id_usuario = 3;
-- Eliminar con subconsulta
DELETE FROM pedidos_productos
WHERE id_pedido IN (
  SELECT id_pedido FROM pedidos WHERE total = 0
);
-- Vaciar tabla conservando la estructura (más rápido que DELETE sin WHERE)
TRUNCATE TABLE log_accesos;
-- CUIDADO: DELETE sin WHERE elimina todas las filas
-- DELETE FROM tabla;  ← no ejecutar sin WHERE

Estructura de tablas

-- Crear tabla
CREATE TABLE clientes (
  id_cliente    INT           PRIMARY KEY AUTO_INCREMENT,
  nombre        VARCHAR(100)  NOT NULL,
  email         VARCHAR(150)  UNIQUE NOT NULL,
  ciudad        VARCHAR(100),
  fecha_alta    DATE          DEFAULT CURRENT_DATE
);
-- Añadir columna
ALTER TABLE usuarios ADD COLUMN telefono VARCHAR(20);
-- Modificar tipo de columna
ALTER TABLE productos MODIFY COLUMN precio DECIMAL(10,2) NOT NULL;
-- Renombrar columna (MySQL 8+ / PostgreSQL)
ALTER TABLE usuarios RENAME COLUMN ciudad TO localidad;
-- Eliminar columna
ALTER TABLE usuarios DROP COLUMN telefono;
-- Eliminar tabla (irreversible)
DROP TABLE IF EXISTS log_accesos;
-- Ver estructura de una tabla (MySQL)
DESCRIBE usuarios;
SHOW CREATE TABLE usuarios;

Índices

-- Crear índice simple
CREATE INDEX idx_usuarios_email ON usuarios(email);
-- Índice único (impide duplicados)
CREATE UNIQUE INDEX idx_usuarios_email_unico ON usuarios(email);
-- Índice compuesto (útil cuando filtras por las dos columnas juntas)
CREATE INDEX idx_pedidos_usuario_fecha ON pedidos(id_usuario, fecha);
-- Ver índices de una tabla (MySQL)
SHOW INDEX FROM usuarios;
-- Eliminar índice
DROP INDEX idx_usuarios_email ON usuarios;   -- MySQL
DROP INDEX idx_usuarios_email;               -- PostgreSQL
-- Analizar si una consulta usa índices
EXPLAIN SELECT * FROM usuarios WHERE email = 'ana@mail.com';
EXPLAIN ANALYZE SELECT * FROM usuarios WHERE email = 'ana@mail.com';  -- PostgreSQL

Transacciones

-- Transacción básica
BEGIN;
  UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
  UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;
COMMIT;
-- Deshacer si algo falla
BEGIN;
  UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
  -- algo sale mal
ROLLBACK;
-- Punto de guardado parcial
BEGIN;
  UPDATE pedidos SET estado = 'enviado' WHERE id = 101;
  SAVEPOINT despues_pedido;
  UPDATE stock SET cantidad = cantidad - 1 WHERE id_producto = 5;
  -- si el UPDATE de stock falla, volvemos al savepoint
  ROLLBACK TO SAVEPOINT despues_pedido;
COMMIT;
-- Nivel de aislamiento (antes del BEGIN)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
  -- operaciones
COMMIT;

Funciones útiles

-- Texto
SELECT UPPER(nombre) FROM usuarios;          -- MAYÚSCULAS
SELECT LOWER(nombre) FROM usuarios;          -- minúsculas
SELECT LENGTH(nombre) FROM usuarios;         -- longitud en caracteres
SELECT TRIM(nombre) FROM usuarios;           -- elimina espacios al inicio/fin
SELECT CONCAT(nombre, ' — ', email) FROM usuarios;
SELECT SUBSTRING(email, 1, 5) FROM usuarios; -- primeros 5 caracteres
-- Números
SELECT ROUND(precio, 2) FROM productos;     -- redondear
SELECT CEIL(precio)     FROM productos;     -- redondear arriba
SELECT FLOOR(precio)    FROM productos;     -- redondear abajo
SELECT ABS(-50);                            -- valor absoluto
-- Fechas
SELECT NOW();                               -- fecha y hora actual
SELECT CURDATE();                           -- solo fecha actual (MySQL)
SELECT CURRENT_DATE;                        -- solo fecha actual (PostgreSQL)
SELECT YEAR(fecha), MONTH(fecha) FROM pedidos;
SELECT DATEDIFF(NOW(), fecha) AS dias_desde_pedido FROM pedidos;
-- Control de nulos
SELECT COALESCE(telefono, 'sin teléfono') FROM usuarios;  -- primer valor no nulo
SELECT IFNULL(descuento, 0) FROM productos;               -- MySQL
SELECT NULLIF(cantidad, 0) FROM productos;                -- devuelve NULL si son iguales
-- Condicional en columna
SELECT
  nombre,
  CASE
    WHEN precio < 20  THEN 'económico'
    WHEN precio < 100 THEN 'medio'
    ELSE 'premium'
  END AS rango_precio
FROM productos;