SQL · Guías · 30 de octubre de 2026 · 14 min de lectura
Chuleta SQL para analistas de datos: cláusulas y funciones con ejemplos que puedes ejecutar

Este es el SQL que un analista usa a diario, en una sola página: las cláusulas de una consulta, los filtros, los agregados, los JOIN, CASE, el manejo de NULL, las subconsultas y los CTE, las funciones de ventana y las funciones de fecha y de texto que más se usan. Cada apartado tiene una tabla de referencia corta y un ejemplo con su salida real, para que veas lo que devuelve la sintaxis en lugar de adivinarlo.
Todos los ejemplos usan la base de datos de una aerolínea del curso de SQL de Nomto: 104 aeropuertos, 9 modelos de avión, 33.121 vuelos del 12 de julio al 10 de septiembre de 2026 y 262.788 reservas, con el dinero en dólares y las horas en hora de Moscú (valores en inglés). La sintaxis es la de PostgreSQL. Puedes pegar cualquier consulta en la sala de SQL de Nomto y ejecutarla tal cual.
Las tablas de los ejemplos
| Tabla | Una fila es | Columnas que se usan aquí |
|---|---|---|
airports |
un aeropuerto | airport_code, airport_name, city, timezone |
aircrafts |
un modelo de avión | aircraft_code, model, range (km) |
flights |
una salida en un día concreto | flight_id, flight_no, scheduled_departure, actual_departure, departure_airport, arrival_airport, status, aircraft_code |
bookings |
una reserva | book_ref, book_date, total_amount |
tickets |
un pasajero de una reserva | ticket_no, book_ref, passenger_name, contact_data (JSON) |
ticket_flights |
un tramo de vuelo de un billete | ticket_no, flight_id, fare_conditions, amount |
Esqueleto de una consulta: las cláusulas y su orden
| Cláusula | Qué hace | Se ejecuta |
|---|---|---|
SELECT |
columnas y expresiones que se devuelven | 5.ª |
FROM / JOIN |
de dónde salen las filas | 1.ª |
WHERE |
conserva las filas que cumplen una condición | 2.ª |
GROUP BY |
una fila por grupo | 3.ª |
HAVING |
conserva los grupos que cumplen una condición | 4.ª |
ORDER BY |
ordena el resultado | 6.ª |
LIMIT |
conserva las primeras N filas | 7.ª |
Las escribes en el orden de la primera columna de la tabla, pero la base de datos las ejecuta en el orden de la última. Por eso un alias definido en SELECT funciona en ORDER BY pero no en WHERE.
SELECT model, range
FROM aircrafts
WHERE range >= 5000
ORDER BY range DESC
LIMIT 3;
| model | range |
|---|---|
| Boeing 777-300 | 11100 |
| Boeing 767-300 | 7900 |
| Airbus A319-100 | 6700 |
Filtros (WHERE)
| Operador | Ejemplo | Nota |
|---|---|---|
=, <>, <, >= |
range >= 5000 |
el texto va entre comillas simples |
AND, OR, NOT |
a AND (b OR c) |
AND tiene más prioridad que OR: usa paréntesis |
IN |
status IN ('Departed', 'Arrived') |
atajo para varios OR |
BETWEEN |
x BETWEEN 1 AND 10 |
incluye los dos extremos |
LIKE / ILIKE |
city LIKE 'Novo%' |
% cualquier número de caracteres, _ un carácter; ILIKE no distingue mayúsculas |
IS NULL |
actual_departure IS NULL |
nunca = NULL |
SELECT COUNT(*) FILTER (WHERE status IN ('Departed', 'Arrived')) AS flown,
COUNT(*) FILTER (WHERE scheduled_departure BETWEEN '2026-08-01' AND '2026-08-07 23:59:59') AS first_week_aug,
COUNT(*) FILTER (WHERE actual_departure IS NULL) AS not_departed
FROM flights;
| flown | first_week_aug | not_departed |
|---|---|---|
| 16765 | 3798 | 16348 |
FILTER (WHERE ...) permite meter tres filtros distintos en una sola consulta: cada COUNT ve solo sus filas. Para rangos de fechas, >= '2026-08-01' AND < '2026-08-08' es más seguro que BETWEEN, porque no tienes que acordarte del 23:59:59.
Agregados
| Función | Devuelve |
|---|---|
COUNT(*) |
número de filas |
COUNT(col) |
número de valores que no son NULL |
COUNT(DISTINCT col) |
número de valores distintos que no son NULL |
SUM(col), AVG(col) |
suma y media (ignoran los NULL) |
MIN(col), MAX(col) |
el menor y el mayor (también con fechas y texto) |
SELECT COUNT(*) AS bookings,
COUNT(DISTINCT DATE_TRUNC('day', book_date)) AS days,
SUM(total_amount) AS revenue,
ROUND(AVG(total_amount), 2) AS avg_booking,
MIN(total_amount) AS min_b,
MAX(total_amount) AS max_b
FROM bookings;
| bookings | days | revenue | avg_booking | min_b | max_b |
|---|---|---|---|---|---|
| 262788 | 56 | 207669809.00 | 790.26 | 34.00 | 12045.00 |
GROUP BY y HAVING
Cada columna del SELECT tiene que estar en GROUP BY o dentro de un agregado. WHERE filtra filas antes de agrupar; HAVING filtra grupos después.
SELECT city, COUNT(*) AS airports
FROM airports
GROUP BY city
HAVING COUNT(*) > 1
ORDER BY airports DESC, city;
| city | airports |
|---|---|
| Moscow | 3 |
| Ulyanovsk | 2 |
La diferencia, con detalle, en GROUP BY y HAVING.
JOIN
| JOIN | Conserva |
|---|---|
JOIN (INNER JOIN) |
solo las filas con pareja en los dos lados |
LEFT JOIN |
todas las filas de la tabla izquierda; NULL donde la derecha no tiene pareja |
RIGHT JOIN |
el espejo de LEFT JOIN (se usa poco: mejor intercambia las tablas) |
FULL JOIN |
todas las filas de los dos lados |
CROSS JOIN |
todas las combinaciones de filas |
LEFT JOIN ... WHERE right.key IS NULL |
las filas sin pareja (un anti join) |
Buscar un valor en otra tabla:
SELECT f.flight_no, a.model
FROM flights f
JOIN aircrafts a ON a.aircraft_code = f.aircraft_code
WHERE f.flight_id = 1;
| flight_no | model |
|---|---|
| PG0405 | Airbus A321-200 |
Encontrar filas sin pareja, aquí el modelo de avión que nunca vuela:
SELECT a.model
FROM aircrafts a
LEFT JOIN flights f ON f.aircraft_code = a.aircraft_code
WHERE f.flight_id IS NULL;
| model |
|---|
| Airbus A320-200 |
La trampa que hay que recordar: cuando una fila coincide con varias, se repite una vez por cada coincidencia. Sumar bookings.total_amount después de unir con tickets da 343.206.192 en lugar de los 207.669.809 correctos, porque las reservas con varios billetes se cuentan varias veces. La guía de JOIN en SQL explica esta multiplicación de filas con detalle.
CASE: lógica si-entonces
CASE WHEN condition1 THEN value1
WHEN condition2 THEN value2
ELSE default
END
Las condiciones se comprueban de arriba abajo y gana la primera que se cumple, así que pon primero la más estrecha.
SELECT CASE WHEN total_amount >= 1000 THEN 'large'
WHEN total_amount >= 300 THEN 'medium'
ELSE 'small'
END AS size,
COUNT(*) AS bookings
FROM bookings
GROUP BY size
ORDER BY bookings DESC;
| size | bookings |
|---|---|
| medium | 130009 |
| small | 68305 |
| large | 64474 |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) es la forma portable de contar por condición; COUNT(*) FILTER (WHERE ...) hace lo mismo en PostgreSQL.
Manejo de NULL
| Expresión | Qué hace |
|---|---|
col IS NULL / IS NOT NULL |
la única forma correcta de comprobar NULL |
COALESCE(a, b, ...) |
el primer valor que no es NULL |
NULLIF(a, b) |
NULL si a = b; si no, a (útil para evitar dividir entre cero) |
COUNT(col) |
se salta los NULL; COUNT(*) no |
SELECT flight_no, scheduled_departure, status,
COALESCE(CAST(actual_departure AS VARCHAR), 'not departed') AS actual
FROM flights
WHERE flight_no = 'PG0405'
AND scheduled_departure BETWEEN '2026-08-10' AND '2026-08-13'
ORDER BY scheduled_departure;
| flight_no | scheduled_departure | status | actual |
|---|---|---|---|
| PG0405 | 2026-08-10 09:35:00 | Arrived | 2026-08-10 09:37:00 |
| PG0405 | 2026-08-11 09:35:00 | Arrived | 2026-08-11 09:38:00 |
| PG0405 | 2026-08-12 09:35:00 | On Time | not departed |
Un porcentaje seguro, que devuelve NULL en lugar de un error si la tabla está vacía:
SELECT ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'Cancelled') / NULLIF(COUNT(*), 0), 2) AS cancelled_pct
FROM flights;
| cancelled_pct |
|---|
| 1.25 |
Escribe 100.0, no 100: con dos enteros, algunas bases de datos hacen división entera y devuelven 0.
Subconsultas y CTE
Una subconsulta es una consulta dentro de otra. Las reservas por encima de la media:
SELECT COUNT(*) AS above_avg
FROM bookings
WHERE total_amount > (SELECT AVG(total_amount) FROM bookings);
| above_avg |
|---|
| 87224 |
Un CTE (WITH) le pone nombre a una subconsulta para que la consulta se lea de arriba abajo. Vuelos por día y, después, el mínimo, la media y el máximo de esos recuentos diarios:
WITH daily AS (
SELECT CAST(scheduled_departure AS DATE) AS day, COUNT(*) AS flights
FROM flights
GROUP BY day
)
SELECT MIN(flights) AS min_day, ROUND(AVG(flights), 1) AS avg_day, MAX(flights) AS max_day
FROM daily;
| min_day | avg_day | max_day |
|---|---|---|
| 493 | 543 | 568 |
Usa un CTE siempre que tengas que agregar dos veces (una media de recuentos diarios, una cuota sobre un total) o cuando la consulta ocupe más de una pantalla.
Funciones de ventana
Una función de ventana añade un valor a cada fila sin colapsar las filas. La forma es función() OVER (PARTITION BY ... ORDER BY ... marco).
| Función | Para qué sirve |
|---|---|
ROW_NUMBER() |
top N por grupo, quitar duplicados |
RANK(), DENSE_RANK() |
rankings con empates (RANK deja huecos, DENSE_RANK no) |
LAG(col), LEAD(col) |
valor de la fila anterior / siguiente |
SUM(col) OVER (ORDER BY ...) |
total acumulado |
AVG(col) OVER (ORDER BY ... ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) |
media móvil de 7 filas |
SUM(col) OVER () |
total general junto a cada fila (para calcular cuotas) |
Comparación con el día anterior y total acumulado en una sola consulta:
SELECT CAST(book_date AS DATE) AS day,
COUNT(*) AS bookings,
LAG(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS prev_day,
SUM(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS running_total
FROM bookings
GROUP BY day
ORDER BY day
LIMIT 4;
| day | bookings | prev_day | running_total |
|---|---|---|---|
| 2026-06-17 | 3 | NULL | 3 |
| 2026-06-18 | 7 | 3 | 10 |
| 2026-06-19 | 20 | 7 | 30 |
| 2026-06-20 | 74 | 20 | 104 |
El primero de cada grupo con ROW_NUMBER: el aeropuerto de salida con más vuelos para cada modelo de avión.
SELECT *
FROM (
SELECT a.model, f.departure_airport, COUNT(*) AS flights,
ROW_NUMBER() OVER (PARTITION BY a.model ORDER BY COUNT(*) DESC) AS rn
FROM flights f
JOIN aircrafts a USING (aircraft_code)
GROUP BY a.model, f.departure_airport
) t
WHERE rn = 1
ORDER BY flights DESC
LIMIT 4;
| model | departure_airport | flights | rn |
|---|---|---|---|
| Sukhoi Superjet-100 | SVO | 1011 | 1 |
| Bombardier CRJ-200 | DME | 829 | 1 |
| Airbus A321-200 | LED | 793 | 1 |
| Cessna 208 Caravan | KJA | 488 | 1 |
No se puede filtrar por una función de ventana en el WHERE de la misma consulta; de ahí la subconsulta. La explicación completa, en funciones de ventana SQL y en running totals (en inglés).
Fechas y horas
| Expresión | Resultado |
|---|---|
CAST(ts AS DATE) |
quita la hora |
DATE_TRUNC('month', ts) |
primer instante del mes (también 'week', 'day', 'hour') |
EXTRACT(MONTH FROM ts) |
número de mes; también YEAR, DAY, HOUR, ISODOW (1 = lunes) |
ts + INTERVAL '30 days' |
aritmética de fechas |
date1 - date2 |
días entre dos fechas |
EXTRACT(EPOCH FROM ts1 - ts2) / 60 |
minutos entre dos marcas de tiempo |
Vuelos por mes:
SELECT DATE_TRUNC('month', scheduled_departure) AS month, COUNT(*) AS flights
FROM flights
GROUP BY month
ORDER BY month;
| month | flights |
|---|---|
| 2026-07-01 00:00:00 | 10826 |
| 2026-08-01 00:00:00 | 16854 |
| 2026-09-01 00:00:00 | 5441 |
Julio y septiembre son meses incompletos (el calendario va del 12 de julio al 10 de septiembre), algo que conviene decir en voz alta siempre que compares meses.
Sumar y restar fechas:
SELECT book_ref, book_date,
book_date + INTERVAL '30 days' AS plus_30,
'2026-08-11'::DATE - CAST(book_date AS DATE) AS days_ago
FROM bookings
ORDER BY book_ref
LIMIT 2;
| book_ref | book_date | plus_30 | days_ago |
|---|---|---|---|
| 00000F | 2026-07-01 03:12:00 | 2026-07-31 03:12:00 | 41 |
| 000012 | 2026-07-10 09:02:00 | 2026-08-09 09:02:00 | 32 |
Los mayores retrasos de salida, en minutos:
SELECT flight_no, scheduled_departure, actual_departure,
ROUND(EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60) AS delay_min
FROM flights
WHERE status = 'Arrived'
ORDER BY delay_min DESC
LIMIT 3;
| flight_no | scheduled_departure | actual_departure | delay_min |
|---|---|---|---|
| PG0589 | 2026-07-25 15:30:00 | 2026-07-25 20:07:00 | 277 |
| PG0164 | 2026-07-25 15:25:00 | 2026-07-25 19:53:00 | 268 |
| PG0364 | 2026-07-15 11:45:00 | 2026-07-15 16:12:00 | 267 |
Para los días sin ninguna fila (un día con cero reservas simplemente no aparece en GROUP BY), mira la guía de date spine (en inglés).
Texto
| Función | Ejemplo | Resultado |
|---|---|---|
|| |
'PG' || '0405' |
PG0405 |
UPPER, LOWER |
LOWER('SVO') |
svo |
LENGTH |
LENGTH('Vnukovo') |
7 |
TRIM |
TRIM(' SVO ') |
SVO |
LEFT(s, n), SUBSTRING(s, start, len) |
LEFT('Moscow', 3) |
Mos |
POSITION(sub IN s) |
POSITION('/' IN 'Europe/Moscow') |
7 |
REPLACE(s, from, to) |
REPLACE('Asia/Ust-Nera', '-', ' ') |
Asia/Ust Nera |
SPLIT_PART(s, sep, n) |
SPLIT_PART('IVAN PETROV', ' ', 2) |
PETROV |
json ->> 'key' |
contact_data ->> 'phone' |
el valor como texto |
Los nombres de los pasajeros están en mayúsculas con la forma «NOMBRE APELLIDO», y los contactos son un JSON:
SELECT passenger_name,
LOWER(SPLIT_PART(passenger_name, ' ', 2)) AS last_lower,
contact_data ->> 'phone' AS phone
FROM tickets
ORDER BY ticket_no
LIMIT 3;
| passenger_name | last_lower | phone |
|---|---|---|
| VALERIY TIKHONOV | tikhonov | +70127117011 |
| EVGENIYA ALEKSEEVA | alekseeva | +70378089255 |
| ARTUR GERASIMOV | gerasimov | +70760429203 |
Montar una etiqueta y cortar una cadena en un carácter:
SELECT airport_name || ' (' || airport_code || ')' AS label,
SUBSTRING(timezone, 1, POSITION('/' IN timezone) - 1) AS region
FROM airports
WHERE city = 'Moscow'
ORDER BY airport_code;
| label | region |
|---|---|
| Domodedovo (DME) | Europe |
| Sheremetyevo (SVO) | Europe |
| Vnukovo (VKO) | Europe |
Todas las funciones de texto, con más ejemplos, en funciones de texto en SQL.
Errores de los que te salva esta chuleta
= NULL. SiempreIS NULL; la igualdad no devuelve filas ni da error.LIMITsinORDER BY. Obtienes unas filas cualesquiera, no las primeras.- Sumar después de un JOIN uno a muchos. Agrega primero el lado «muchos» y luego une.
- División entera.
100 * a / bpuede redondear a 0; escribe100.0. - Filtrar una función de ventana en
WHERE. Envuélvela en una subconsulta o un CTE. BETWEENcon marcas de tiempo.'2026-08-07'significa medianoche; usa>=inicio y<día siguiente.
Diferencias entre dialectos
Todo lo anterior es PostgreSQL. Casi todo es igual en otras bases de datos; las diferencias habituales son estas:
| Tarea | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| primeras N filas | LIMIT 10 |
LIMIT 10 |
TOP 10 / FETCH FIRST 10 ROWS ONLY |
| concatenar | a || b |
CONCAT(a, b) |
a + b o CONCAT(a, b) |
| truncar una fecha | DATE_TRUNC('month', d) |
DATE_FORMAT(d, '%Y-%m-01') |
DATETRUNC(month, d) (2022+) |
| LIKE sin distinguir mayúsculas | ILIKE |
LIKE (según la intercalación) |
LIKE (según la intercalación) |
| conteo condicional | COUNT(*) FILTER (WHERE ...) |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) |
Cómo usar esta chuleta
Tenla abierta mientras trabajas, pero no intentes memorizarla. La sintaxis se recuerda usándola en preguntas que te importan. En la sala de SQL de Nomto todas las consultas de arriba se ejecutan sobre la misma base de datos de la aerolínea, así que cambia un filtro o una columna de agrupación y mira qué se mueve. Si estás empezando, las consultas SQL básicas explican paso a paso la primera mitad de esta página, y los ejercicios SQL resueltos te dan tareas para aplicar la segunda.
Preguntas frecuentes
¿Qué SQL tiene que saber un analista de datos?
Con soltura: SELECT, WHERE, GROUP BY, HAVING, JOIN, CASE, manejo de NULL, subconsultas y CTE, y funciones de fecha. Después, funciones de ventana (ROW_NUMBER, LAG, totales acumulados), que salen en la mayoría de las entrevistas para analista. Los índices, los procedimientos almacenados y la administración de bases de datos importan mucho menos para este puesto.
¿Esta chuleta sirve para MySQL y SQL Server?
Las cláusulas, los JOIN, los agregados, CASE, COALESCE, las subconsultas, los CTE y las funciones de ventana funcionan igual. Cambian las funciones de fecha, la concatenación de cadenas y LIMIT; la tabla de dialectos de arriba recoge los casos habituales.
¿Qué diferencia hay entre una subconsulta y un CTE?
Hacen el mismo trabajo. Un CTE le pone nombre a la subconsulta y la sube al principio, así que una consulta larga se lee por pasos. Usa la que se lea mejor; para cualquier cosa de dos pasos o más, suele ser el CTE.
¿Puedo ejecutar estos ejemplos sin instalar una base de datos?
Sí. Abre la sala de SQL de Nomto, pega una consulta y ejecútala en el navegador sobre los mismos datos de la aerolínea.
También en English, Português (Brasil), Tiếng Việt, 简体中文