SQL · Guías · 29 de octubre de 2026 · 11 min de lectura

Preguntas de entrevista SQL para analistas de datos: 15 preguntas con respuesta

Una entrevista de trabajo: una candidata con un portátil que muestra una tabla y un entrevistador con una tarjeta de preguntas

En una entrevista SQL para analista de datos casi siempre aparecen dos tipos de preguntas: teóricas (orden de ejecución, WHERE frente a HAVING, tipos de JOIN, NULL, UNION frente a UNION ALL, funciones de ventana) y prácticas, donde escribes una consulta en vivo: el segundo valor más alto, duplicados, top N por grupo, usuarios activos diarios, un embudo de conversión o días consecutivos. Lo que se evalúa no es solo que la consulta funcione, sino que preguntes por los casos límite (empates, NULL, periodos incompletos) y que detectes cuándo un JOIN multiplica filas.

Aquí tienes las 15 preguntas que más se repiten, con respuesta breve y, en las prácticas, una solución ejecutada sobre la base de datos de una aerolínea del curso de SQL de Nomto. Los números son la salida real; puedes ejecutar y modificar cada consulta en la sala de SQL de Nomto.

Los datos de los ejemplos

Preguntas teóricas

1. ¿En qué orden se ejecuta una consulta SQL?

FROM y JOIN → WHERE → GROUP BY → HAVING → SELECT (incluidas las funciones de ventana) → DISTINCT → ORDER BY → LIMIT. De este orden salen las respuestas a media entrevista: por qué un alias del SELECT no se puede usar en WHERE pero sí en ORDER BY, por qué los agregados se filtran en HAVING y por qué una función de ventana no puede ir en WHERE.

2. ¿Qué diferencia hay entre WHERE y HAVING?

WHERE filtra filas antes de agrupar y no admite agregados. HAVING filtra grupos después de agrupar. Si una condición se puede poner en WHERE (por ejemplo, status = 'Arrived'), ponla ahí: se descartan filas antes y la consulta es más barata. En HAVING va solo lo que depende del agregado, como HAVING COUNT(*) > 100.

3. ¿Qué tipos de JOIN hay?

Pregunta de seguimiento habitual: ¿qué pasa si en un LEFT JOIN pones una condición sobre la tabla derecha en el WHERE? Las filas sin pareja tienen NULL en esa columna, la condición las descarta y el LEFT JOIN se convierte en un INNER JOIN. Esa condición va en el ON.

4. COUNT(*), COUNT(columna) y COUNT(DISTINCT columna)

SELECT COUNT(*)                AS total,
       COUNT(actual_departure) AS departed
FROM flights;
total departed
33121 16773

COUNT(*) cuenta filas; COUNT(columna) ignora los NULL (vuelos que aún no han salido); COUNT(DISTINCT columna) cuenta valores distintos. En airports hay 104 filas y 101 ciudades distintas, porque Moscú tiene tres aeropuertos.

5. ¿UNION o UNION ALL?

UNION elimina duplicados (y para eso ordena o usa un hash, lo cual cuesta); UNION ALL pega los resultados tal cual. Si unes las columnas departure_airport y arrival_airport de flights, UNION devuelve 104 aeropuertos y UNION ALL, 66 242 filas (dos por vuelo). Usa UNION ALL salvo que necesites quitar duplicados de verdad.

6. ¿Por qué NOT IN puede devolver cero filas?

Es la trampa de NULL favorita de los entrevistadores. ¿Qué modelos de avión no tienen vuelos?

SELECT COUNT(*)
FROM aircrafts
WHERE aircraft_code NOT IN (SELECT aircraft_code FROM flights);

Devuelve 1 (el Airbus A320-200). Pero si la subconsulta contuviera un solo NULL, el resultado sería 0: x NOT IN (a, b, NULL) equivale a x <> a AND x <> b AND x <> NULL, y la última comparación es «desconocida», así que ninguna fila pasa. Lo comprobamos añadiendo un NULL a la lista con UNION ALL SELECT NULL: el conteo cae a 0. La forma segura es NOT EXISTS, que da 1 en ambos casos:

SELECT COUNT(*)
FROM aircrafts a
WHERE NOT EXISTS (
  SELECT 1 FROM flights f WHERE f.aircraft_code = a.aircraft_code
);

7. ROW_NUMBER, RANK y DENSE_RANK

Las tres numeran según un orden y difieren en los empates. Con valores 10, 9, 9, 8: ROW_NUMBER da 1, 2, 3, 4; RANK da 1, 2, 2, 4; DENSE_RANK da 1, 2, 2, 3. ROW_NUMBER para deduplicar y quedarte con una fila por grupo; DENSE_RANK para «el N-ésimo valor distinto». La explicación completa, con ejemplos ejecutados, está en funciones de ventana SQL.

8. ¿CTE o subconsulta?

Para el motor suelen ser equivalentes; en PostgreSQL desde la versión 12 una CTE simple se integra en la consulta como una subconsulta. La diferencia es de legibilidad: una CTE (WITH) deja leer la consulta de arriba abajo, paso a paso, y permite reutilizar un resultado intermedio. En una entrevista, escribir en CTE con nombres claros ayuda a que el entrevistador siga tu razonamiento. Solo las CTE pueden ser recursivas (WITH RECURSIVE), por ejemplo para recorrer una red de rutas.

Preguntas prácticas

9. ¿Por qué la suma sale más grande de lo que debería?

Te piden los ingresos de las reservas del 1 de agosto y, de paso, el número de pasajeros. Una solución que parece razonable:

SELECT SUM(b.total_amount) AS revenue,
       COUNT(*)            AS passengers
FROM bookings b
JOIN tickets t ON t.book_ref = b.book_ref
WHERE b.book_date >= '2026-08-01' AND b.book_date < '2026-08-02';
revenue passengers
7497632.00 8064

Los pasajeros están bien, pero los ingresos están inflados. La suma correcta, sin el JOIN, es 4 504 496:

SELECT SUM(total_amount) AS revenue
FROM bookings
WHERE book_date >= '2026-08-01' AND book_date < '2026-08-02';

Ese día hubo 5 746 reservas y 8 064 billetes. Al unir con tickets, una reserva con tres billetes aparece tres veces y su total_amount se suma tres veces. Es la multiplicación de filas (fan-out): el error más caro en informes reales, porque no da ningún mensaje. Soluciones: agrega cada tabla a su granularidad antes de unir (una CTE con billetes por reserva) o suma solo en la tabla del nivel correcto. Señal de alarma: si después de un JOIN el número de filas crece, cualquier SUM de la tabla «de arriba» está mal.

10. El segundo valor más alto

SELECT MAX(total_amount)
FROM bookings
WHERE total_amount < (SELECT MAX(total_amount) FROM bookings);

Resultado: 10871.00 (el máximo es 12045.00). Para el N-ésimo, usa DENSE_RANK() OVER (ORDER BY total_amount DESC) en una subconsulta y filtra rnk = N. Antes de escribir, pregunta: ¿si hay empates en el máximo, el «segundo» es el siguiente valor distinto? ¿Qué devolvemos si no existe (tabla con un solo valor)? La versión con MAX devuelve NULL, lo cual suele ser la respuesta correcta.

11. Encontrar y eliminar duplicados

En app_events cada evento tiene su event_id, pero algunos eventos llegaron dos veces desde el dispositivo, con el mismo cliente, nombre y hora. Primero, cuántos hay:

SELECT COUNT(*)     AS groups,
       SUM(n - 1)   AS extra_rows
FROM (
  SELECT client_id, event_name, event_time, COUNT(*) AS n
  FROM app_events
  GROUP BY client_id, event_name, event_time
  HAVING COUNT(*) > 1
) d;
groups extra_rows
10689 10689

Luego, quedarse con una copia de cada uno (la que llegó primero):

WITH d AS (
  SELECT *,
         ROW_NUMBER() OVER (
           PARTITION BY client_id, event_name, event_time
           ORDER BY received_at, event_id
         ) AS rn
  FROM app_events
)
SELECT COUNT(*) FROM d WHERE rn = 1;

Quedan 1 484 034 de 1 494 723 filas: exactamente 10 689 menos. Comprobar que la resta cuadra es lo que convence al entrevistador de que entiendes lo que hace la consulta. Menciona también que DISTINCT no sirve aquí, porque event_id y received_at son distintos en las dos copias.

12. Usuarios activos diarios (DAU)

SELECT CAST(event_time AS DATE)   AS day,
       COUNT(DISTINCT client_id)  AS dau
FROM app_events
WHERE event_time >= '2026-08-03' AND event_time < '2026-08-10'
GROUP BY CAST(event_time AS DATE)
ORDER BY day;
day dau
2026-08-03 6166
2026-08-04 6285
2026-08-05 6405
2026-08-06 6474
2026-08-07 8350
2026-08-08 6868
2026-08-09 6810

La clave es COUNT(DISTINCT client_id): un usuario con veinte eventos cuenta una vez. La pregunta de seguimiento es casi segura: ¿qué pasó el 7 de agosto? Ese día las aperturas de notificaciones push (push_open) se multiplicaron por tres y media: 2 928 frente a 830 el día anterior. Un buen analista no explica el pico de memoria: lo busca en los datos, desglosando por tipo de evento, plataforma o fuente.

13. Embudo de conversión

¿Cuántos clientes que buscaron un vuelo en la semana del 3 al 9 de agosto llegaron a elegir tarifa, a iniciar el pago y a comprar?

WITH s AS (
  SELECT client_id,
         MAX(CASE WHEN event_name = 'search'         THEN 1 ELSE 0 END) AS s1,
         MAX(CASE WHEN event_name = 'select_fare'    THEN 1 ELSE 0 END) AS s2,
         MAX(CASE WHEN event_name = 'checkout_start' THEN 1 ELSE 0 END) AS s3,
         MAX(CASE WHEN event_name = 'purchase'       THEN 1 ELSE 0 END) AS s4
  FROM app_events
  WHERE event_time >= '2026-08-03' AND event_time < '2026-08-10'
  GROUP BY client_id
)
SELECT SUM(s1)                AS search,
       SUM(s1 * s2)           AS select_fare,
       SUM(s1 * s2 * s3)      AS checkout_start,
       SUM(s1 * s2 * s3 * s4) AS purchase
FROM s;
search select_fare checkout_start purchase
26868 17452 15272 9246

La conversión de búsqueda a compra es 9 246 / 26 868 ≈ 34.4 %. Multiplicar las banderas hace que cada paso cuente solo a quien hizo todos los anteriores; sin eso, un cliente que compró sin buscar en esa semana entraría en el último paso y no en el primero. Este embudo no exige el orden temporal de los pasos; si te lo piden, compara las fechas mínimas de cada evento (MIN(CASE WHEN ... THEN event_time END)). Comentar esa limitación vale tanto como la consulta.

14. Días consecutivos de actividad

¿Cuántos socios del programa de fidelidad usaron la app al menos 7 días seguidos? Es el problema de gaps and islands: si a cada fecha le restas su número de fila, los días consecutivos dan el mismo resultado.

WITH d AS (
  SELECT DISTINCT member_id, CAST(event_time AS DATE) AS day
  FROM app_events
  WHERE member_id IS NOT NULL
),
g AS (
  SELECT member_id,
         day,
         day - CAST(ROW_NUMBER() OVER (PARTITION BY member_id ORDER BY day) AS INTEGER) AS grp
  FROM d
),
s AS (
  SELECT member_id, grp, COUNT(*) AS streak
  FROM g
  GROUP BY member_id, grp
)
SELECT COUNT(DISTINCT member_id) AS members_7plus,
       MAX(streak)               AS longest
FROM s
WHERE streak >= 7;
members_7plus longest
1897 42

1 897 de los 33 624 socios con actividad tuvieron una racha de 7 días o más, y la más larga dura 42 días: todo el periodo de los datos, del 1 de julio al 11 de agosto. El DISTINCT del primer paso es imprescindible: con dos eventos el mismo día, la resta dejaría de funcionar.

15. ¿Media o mediana?

SELECT ROUND(AVG(total_amount), 2)                               AS mean,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_amount) AS median
FROM bookings;

La media es 790.26 y la mediana, 559: unas pocas reservas muy caras (hasta 12 045) tiran de la media hacia arriba, y solo un tercio de las reservas supera la media. Para importes, tiempos y cualquier métrica con cola larga, informa de la mediana o de percentiles, y explica por qué. PERCENTILE_CONT interpola entre valores; PERCENTILE_DISC devuelve un valor que existe en los datos.

Cómo responder en la entrevista

  1. Repite la pregunta con tus palabras y aclara la granularidad. «¿Una fila es una reserva o un billete?» evita la mitad de los errores.
  2. Pregunta por los casos límite antes de escribir: empates, NULL, periodos incompletos (en estos datos el último día termina a las 18:00), usuarios sin actividad.
  3. Escribe en CTE con nombres claros, de lo general a lo concreto.
  4. Comprueba el resultado en voz alta: ¿cuántas filas espero?, ¿la suma por grupos cuadra con el total?
  5. Comenta qué harías con más tiempo: índices, rendimiento, cómo cambiaría la consulta con 100 veces más datos.

Cómo prepararte

Las preguntas teóricas se aprenden leyendo; las prácticas, solo escribiendo. En la sala de SQL de Nomto puedes ejecutar todas las consultas de esta guía y el curso avanzado tiene un módulo entero de problemas de entrevista (N-ésimo valor y mediana, secuencias y días seguidos, buscar la anomalía y explicar una caída), con corrección automática. Para calentar, empieza por los ejercicios SQL resueltos; si necesitas repasar la base, por las consultas SQL básicas.

Preguntas frecuentes

¿Qué nivel de SQL piden para un puesto de analista de datos junior?

Con soltura: SELECT, WHERE, GROUP BY, HAVING, todos los JOIN, CASE, subconsultas y CTE. Cada vez más empresas piden también funciones de ventana (ROW_NUMBER, LAG, acumulados) incluso para puestos junior.

¿En qué dialecto de SQL debo responder?

En el que use la empresa, si lo sabes; si no, en SQL estándar o PostgreSQL, y avisa cuando uses algo específico (ILIKE, DATE_TRUNC). A nadie le importa la sintaxis exacta de una función de fechas; importa la lógica.

¿Cuánto dura una prueba técnica de SQL?

Lo habitual son 30–60 minutos en vivo con 2–4 problemas de dificultad creciente, o una prueba para casa de 2–3 horas con un conjunto de datos y preguntas de negocio.

¿Es mejor responder con subconsultas o con funciones de ventana?

Las dos son válidas. Las funciones de ventana suelen dar consultas más cortas para top N, deduplicación y comparaciones con la fila anterior; las subconsultas, para comparaciones con un único valor (la media, el máximo). Elige la que puedas explicar con claridad.

También en Português (Brasil), Tiếng Việt, English, 简体中文