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

Un escritorio con una chuleta de SQL impresa en una página, un portátil con una consulta y una taza de café

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

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, 简体中文