Saltar al contenido
chuleta.sql · devschool

Chuleta de SQL

Curso de SQL

Resumen de consulta rápida con lo esencial de SQL, pensado para MySQL y MariaDB. Los ejemplos usan las tablas del curso: clientes(id, nombre, ciudad, edad) y pedidos(id, cliente_id, fecha, importe).

SELECT: consultas básicas

SELECT nombre, edad
FROM clientes
WHERE ciudad = 'Santander'
ORDER BY edad DESC
LIMIT 10;
-- Marta Ruiz 45 | Ana García 34
CódigoQué hace
SELECT * FROM clientes;Todas las columnas y filas
SELECT nombre, ciudad FROM clientes;Solo algunas columnas
SELECT nombre AS cliente FROM clientes;Alias de columna
SELECT edad + 1 AS edad_siguiente FROM clientes;Columna calculada
SELECT importe * 1.21 AS con_iva FROM pedidos;Operaciones: + - * / %
SELECT DISTINCT ciudad FROM clientes;Quita repetidos → Santander, Bilbao, Madrid
SELECT CONCAT(nombre, ' (', ciudad, ')') FROM clientes;Une textos → Luis Pérez (Bilbao)
nombre || ' ' || ciudadConcatenar en PostgreSQL y SQLite (en MySQL || es OR)
-- comentario / /* ... */Comentarios
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMITOrden en que se escriben las partes

Más en: ¿Qué es SQL? · SELECT

WHERE y NULL

CódigoQué hace
WHERE edad >= 18Comparaciones: = <> != < > <= >=
WHERE ciudad = 'Madrid' AND edad > 20Las dos condiciones
WHERE ciudad = 'Madrid' OR ciudad = 'Bilbao'Una de las dos
WHERE NOT edad < 18Niega la condición
WHERE ciudad IN ('Bilbao', 'Madrid')Está en la lista → Luis, Pablo, Eva
WHERE ciudad NOT IN ('Madrid')No está en la lista
WHERE edad BETWEEN 20 AND 30Rango con extremos incluidos → Luis, Eva
WHERE fecha >= '2026-09-10'Fechas en formato 'AAAA-MM-DD' → pedidos 3, 4, 5
WHERE nombre LIKE 'M%'Empieza por M → Marta Ruiz
WHERE nombre LIKE '%ez'Termina en ez
WHERE nombre LIKE '_v%'_ es un carácter → Eva Martín
WHERE ciudad IS NULLNo tiene valor
WHERE ciudad IS NOT NULLTiene valor
WHERE ciudad = NULLError típico: nunca es verdadero
NULL + 1NULL (cualquier operación con NULL da NULL)
COALESCE(ciudad, 'sin dato')Primer valor que no sea NULL
IFNULL(ciudad, 'sin dato')Igual, con dos valores (MySQL y SQLite)
NULLIF(a, b)NULL si a = b; si no, a

Cuidado: NOT IN (subconsulta) no devuelve nada si la subconsulta contiene algún NULL. Usa NOT EXISTS.

Más en: WHERE · NULL

ORDER BY y LIMIT

CódigoQué hace
ORDER BY edadDe menor a mayor (ASC, por defecto)
ORDER BY edad DESCDe mayor a menor
ORDER BY ciudad, nombrePor ciudad y, si empatan, por nombre
ORDER BY total DESCSe puede ordenar por un alias
LIMIT 2Solo las 2 primeras filas
LIMIT 2 OFFSET 1Salta 1 y devuelve 2 (paginación)
LIMIT 1, 2Lo mismo en MySQL: LIMIT salto, cantidad
FETCH FIRST 2 ROWS ONLYEstándar (PostgreSQL, Oracle); SQL Server usa TOP

Los NULL van primero en ASC en MySQL y SQLite, y al final en PostgreSQL.

Más en: ORDER BY y LIMIT

Funciones

Código (MySQL)Resultado
UPPER('Ana') / LOWER('Ana')'ANA' / 'ana'
LENGTH('Santander')9 (en bytes; CHAR_LENGTH cuenta caracteres)
SUBSTRING('Ana García', 1, 3)'Ana' (la posición empieza en 1)
REPLACE('Santander', 'Santander', 'SDR')'SDR'
TRIM(' hola ')'hola'
CONCAT('a', 'b')'ab'
ROUND(120.456, 1)120.5
CEIL(3.2) / FLOOR(3.7)4 / 3
ABS(-5)5
MOD(7, 2) o 7 % 21
NOW() / CURDATE()Fecha y hora actuales / fecha de hoy
YEAR(fecha), MONTH(fecha), DAY(fecha)Partes de una fecha
DATEDIFF('2026-09-15', '2026-09-01')14 (días)
DATE_ADD(fecha, INTERVAL 7 DAY)Suma 7 días
DATE_FORMAT(fecha, '%d/%m/%Y')'01/09/2026'
CAST('42' AS SIGNED) + 143 (en PostgreSQL y SQLite: AS INTEGER)

Las funciones de fecha cambian mucho: PostgreSQL usa EXTRACT(YEAR FROM fecha) y SQLite strftime('%Y', fecha).

Más en: Funciones

CASE

SELECT nombre,
       CASE
         WHEN edad < 18 THEN 'menor'
         WHEN edad < 30 THEN 'joven'
         ELSE 'adulto'
       END AS grupo
FROM clientes;
-- Ana adulto | Luis joven | Marta adulto | Pablo menor | Eva joven
CódigoQué hace
CASE ciudad WHEN 'Madrid' THEN 'MAD' ELSE 'otra' ENDCASE simple: compara con valores
CASE WHEN ... END sin ELSEDevuelve NULL si no se cumple nada
SUM(CASE WHEN importe > 50 THEN 1 ELSE 0 END)Cuenta con condición
ORDER BY CASE ciudad WHEN 'Madrid' THEN 1 ELSE 2 ENDOrden a medida
IF(edad >= 18, 'sí', 'no')Atajo de MySQL (no es estándar)

Más en: CASE

Agregados y GROUP BY

CódigoResultado
SELECT COUNT(*) FROM pedidos;5 (filas)
SELECT COUNT(ciudad) FROM clientes;Cuenta los valores no NULL
SELECT COUNT(DISTINCT cliente_id) FROM pedidos;4
SELECT SUM(importe) FROM pedidos;271.74
SELECT AVG(importe) FROM pedidos;54.348
SELECT MIN(importe), MAX(importe) FROM pedidos;12.99, 120.50
SELECT ciudad, COUNT(*) AS total
FROM clientes
GROUP BY ciudad
ORDER BY total DESC, ciudad;
-- Madrid 2 | Santander 2 | Bilbao 1
CódigoQué hace
GROUP BY ciudadUna fila por cada ciudad
GROUP BY ciudad, edadAgrupa por varias columnas
HAVING COUNT(*) > 1Filtra grupos → Madrid (23), Santander (39.5)
WHERE frente a HAVINGWHERE filtra filas antes de agrupar; HAVING, grupos después
GROUP BY YEAR(fecha), MONTH(fecha)Agrupar por una expresión (ventas por mes)

Regla de oro: en el SELECT de una consulta con GROUP BY, cada columna debe estar en el GROUP BY o dentro de una función de agregado.

Orden de ejecución: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

Más en: COUNT, SUM y AVG · GROUP BY

JOIN

SELECT c.nombre, COUNT(p.id) AS pedidos,
       COALESCE(SUM(p.importe), 0) AS gastado
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nombre
ORDER BY gastado DESC;
-- Marta 1 120.50 | Luis 1 60.00 | Ana 2 57.99 | Eva 1 33.25 | Pablo 0 0
CódigoQué hace
FROM clientes c JOIN pedidos p ON p.cliente_id = c.idINNER JOIN: solo filas con pareja
FROM clientes c LEFT JOIN pedidos p ON ...Todos los clientes, tengan pedidos o no
LEFT JOIN ... WHERE p.id IS NULLClientes sin pedidos → Pablo Díaz
RIGHT JOINTodas las filas de la tabla de la derecha
FULL OUTER JOINTodas las de las dos (no existe en MySQL: usa LEFT + UNION + RIGHT)
CROSS JOINTodas las combinaciones (producto cartesiano)
JOIN pedidos USING (cliente_id)Atajo cuando la columna se llama igual en las dos
FROM empleados e JOIN empleados j ON e.jefe_id = j.idSelf join: una tabla consigo misma
JOIN a ON ... JOIN b ON ...Encadenar tres o más tablas

Más en: JOIN · Más JOIN

Subconsultas y UNION

CódigoQué hace
WHERE importe > (SELECT AVG(importe) FROM pedidos)Subconsulta escalar → pedidos 2 y 4
WHERE id IN (SELECT cliente_id FROM pedidos)Clientes con algún pedido
WHERE id NOT IN (SELECT cliente_id FROM pedidos)Clientes sin pedidos → Pablo Díaz
WHERE EXISTS (SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id)Subconsulta correlacionada
SELECT nombre, (SELECT COUNT(*) FROM pedidos p WHERE p.cliente_id = c.id) FROM clientes cSubconsulta en el SELECT
FROM (SELECT ...) AS tTabla derivada (necesita alias)
WHERE importe > ALL (SELECT ...)Mayor que todos (también ANY)
WITH totales AS (
  SELECT cliente_id, SUM(importe) AS total
  FROM pedidos
  GROUP BY cliente_id
)
SELECT cliente_id, total FROM totales WHERE total > 50;
-- 1 57.99 | 2 60.00 | 3 120.50
CódigoQué hace
SELECT ... UNION SELECT ...Junta resultados sin repetidos
SELECT ... UNION ALL SELECT ...Junta con repetidos (más rápido)
SELECT ... INTERSECT SELECT ...Lo que está en los dos (MySQL 8.0.31+)
SELECT ... EXCEPT SELECT ...Lo del primero que no está en el segundo (MySQL 8.0.31+)

Las consultas de un UNION deben tener el mismo número de columnas y tipos compatibles.

Más en: Subconsultas · UNION

INSERT, UPDATE y DELETE

CódigoQué hace
INSERT INTO clientes (id, nombre, ciudad, edad) VALUES (6, 'Sara Gil', 'Bilbao', 31);Añade una fila
INSERT INTO clientes (id, nombre) VALUES (7, 'Iker'), (8, 'Noa');Varias filas; lo que falta queda NULL o DEFAULT
INSERT INTO copia SELECT * FROM clientes WHERE ciudad = 'Madrid';Inserta el resultado de una consulta
UPDATE clientes SET edad = edad + 1 WHERE id = 2;Modifica filas
UPDATE clientes SET ciudad = 'Laredo', edad = 23 WHERE id = 2;Varias columnas a la vez
DELETE FROM pedidos WHERE id = 5;Borra filas
UPDATE o DELETE sin WHEREAfecta a TODAS las filas
TRUNCATE TABLE pedidos;Vacía la tabla entera (rápido, no se deshace en MySQL)
INSERT ... ON DUPLICATE KEY UPDATE edad = VALUES(edad);Inserta o actualiza (MySQL; PostgreSQL y SQLite usan ON CONFLICT)

Consejo: antes de un UPDATE o DELETE, ejecuta un SELECT con el mismo WHERE para ver qué filas cambiarán.

Más en: INSERT, UPDATE, DELETE

CREATE TABLE y restricciones

CREATE TABLE pedidos (
  id INT AUTO_INCREMENT PRIMARY KEY,
  cliente_id INT NOT NULL,
  fecha DATE DEFAULT (CURRENT_DATE),
  importe DECIMAL(10, 2) CHECK (importe >= 0),
  FOREIGN KEY (cliente_id) REFERENCES clientes(id)
    ON DELETE CASCADE
);
CódigoQué hace
CREATE DATABASE tienda CHARACTER SET utf8mb4;Crea una base de datos
USE tienda;La selecciona (MySQL)
CREATE TABLE IF NOT EXISTS ...No da error si ya existe
SHOW TABLES; / DESCRIBE clientes;Lista tablas / muestra columnas (MySQL)
CREATE TABLE copia AS SELECT * FROM clientes;Tabla a partir de una consulta
id INT AUTO_INCREMENTId automático (PostgreSQL: GENERATED ALWAYS AS IDENTITY; SQLite: INTEGER PRIMARY KEY)
Tipo (MySQL)Para qué
INT, BIGINTEnteros
DECIMAL(10, 2)Dinero: decimales exactos
FLOAT, DOUBLEDecimales aproximados
VARCHAR(100)Texto corto de longitud variable
TEXTTexto largo
DATE, DATETIME, TIMESTAMPFecha / fecha y hora
BOOLEANEn MySQL es TINYINT(1): 0 o 1
RestricciónQué asegura
PRIMARY KEYIdentifica cada fila: única y no NULL
NOT NULLObligatorio
UNIQUESin repetidos (por ejemplo, el email)
DEFAULT valorValor si no se indica otro
CHECK (edad >= 0)Condición que debe cumplirse (MySQL 8.0.16+)
FOREIGN KEY ... REFERENCESEl valor debe existir en la otra tabla
ON DELETE CASCADE / SET NULL / RESTRICTQué pasa con los hijos al borrar el padre
CONSTRAINT chk_edad CHECK (...)Pone nombre a una restricción

Más en: CREATE TABLE · Restricciones

ALTER y DROP

CódigoQué hace
ALTER TABLE clientes ADD COLUMN email VARCHAR(150);Añade una columna
ALTER TABLE clientes DROP COLUMN email;Borra una columna
ALTER TABLE clientes RENAME COLUMN ciudad TO localidad;Renombra una columna
ALTER TABLE clientes MODIFY nombre VARCHAR(150) NOT NULL;Cambia el tipo (MySQL; PostgreSQL: ALTER COLUMN ... TYPE)
ALTER TABLE clientes ADD CONSTRAINT uq_email UNIQUE (email);Añade una restricción
ALTER TABLE clientes RENAME TO socios;Renombra la tabla
DROP TABLE IF EXISTS copia;Borra la tabla con sus datos
DROP DATABASE tienda;Borra la base de datos entera

Más en: ALTER y DROP

Vistas e índices

CódigoQué hace
CREATE VIEW v_gasto AS SELECT cliente_id, SUM(importe) AS total FROM pedidos GROUP BY cliente_id;Guarda una consulta con nombre
SELECT * FROM v_gasto WHERE total > 50;Se consulta como una tabla
CREATE OR REPLACE VIEW v_gasto AS ...Cambia la vista (MySQL y PostgreSQL)
DROP VIEW v_gasto;Borra la vista (no los datos)
CREATE INDEX idx_ciudad ON clientes (ciudad);Acelera búsquedas por ciudad
CREATE UNIQUE INDEX idx_email ON clientes (email);Índice que además impide repetidos
CREATE INDEX idx_cli_fecha ON pedidos (cliente_id, fecha);Índice compuesto: el orden importa
EXPLAIN SELECT * FROM clientes WHERE ciudad = 'Madrid';Muestra si se usa un índice
DROP INDEX idx_ciudad ON clientes;Borra un índice (PostgreSQL y SQLite: DROP INDEX idx_ciudad;)

Las claves primarias y las restricciones UNIQUE crean su índice solas. Los índices aceleran las lecturas pero ralentizan un poco las escrituras.

Más en: Vistas · Índices

Transacciones

START TRANSACTION;
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;
COMMIT;   -- o ROLLBACK para deshacer todo
CódigoQué hace
START TRANSACTION; o BEGIN;Empieza una transacción
COMMIT;Confirma los cambios
ROLLBACK;Deshace los cambios desde el inicio
SAVEPOINT paso1;Punto de guardado intermedio
ROLLBACK TO paso1;Vuelve a ese punto
SET autocommit = 0;Desactiva la confirmación automática (MySQL)
ACIDAtomicidad, Consistencia, Aislamiento y Durabilidad

En MySQL solo funcionan con tablas InnoDB (el motor por defecto). CREATE, ALTER y DROP confirman solos y no se pueden deshacer.

Más en: Transacciones

Diseño y permisos

ConceptoResumen
Entidad-relaciónEntidades (tablas), atributos (columnas) y relaciones
Relación 1:NClave foránea en la tabla del lado N (pedidos.cliente_id)
Relación N:MTabla intermedia con dos claves foráneas
1FNValores atómicos: nada de listas en una celda
2FNCada columna depende de toda la clave primaria
3FNNinguna columna depende de otra que no sea clave
Código (MySQL)Qué hace
CREATE USER 'ana'@'localhost' IDENTIFIED BY 'clave';Crea un usuario
GRANT SELECT, INSERT ON tienda.* TO 'ana'@'localhost';Da permisos
GRANT ALL PRIVILEGES ON tienda.* TO 'ana'@'localhost';Todos los permisos sobre una base de datos
SHOW GRANTS FOR 'ana'@'localhost';Muestra sus permisos
REVOKE INSERT ON tienda.* FROM 'ana'@'localhost';Quita permisos
CREATE ROLE lectura; + GRANT lectura TO 'ana'@'localhost';Roles: grupos de permisos
ALTER USER 'ana'@'localhost' IDENTIFIED BY 'nueva';Cambia la contraseña
DROP USER 'ana'@'localhost';Borra el usuario

SQLite no tiene usuarios ni permisos. En PostgreSQL los usuarios son roles y no llevan @'host'.

Más en: Diseño y normalización · Usuarios y permisos

SQL · devschool.es/chuletas/sql/