Saltar al contenido

JOIN, conjuntos y subconsultas

Cómo se consulta más de una tabla a la vez: los tipos de JOIN y lo que devuelve cada uno, los operadores de conjuntos y las consultas anidadas dentro de otra consulta.

Los tipos de JOIN

Un JOIN combina las filas de dos tablas emparejándolas por una condición que se escribe detrás de ON. Lo que distingue a cada tipo es qué hace con las filas que no encuentran pareja: unas se pierden y otras se conservan rellenando con nulos las columnas de la tabla que falta.

TipoQué filas conservaNecesita ON
CROSS JOINTodas las combinaciones posibles: el producto cartesianoNo, no hay condición que aplicar
INNER JOINSolo las parejas que cumplen la condición
LEFT OUTER JOINLas parejas, y además toda fila de la izquierda sin pareja, con nulos a la derecha
RIGHT OUTER JOINLas parejas, y además toda fila de la derecha sin pareja, con nulos a la izquierda
FULL OUTER JOINLas parejas y las filas sueltas de las dos tablas, rellenando con nulos por el lado que falte
NATURAL JOINLas parejas, emparejando por todas las columnas que se llamen igual en las dos tablasNo, la condición la deduce el gestor

La palabra INNER es opcional: escribir JOIN a secas es exactamente lo mismo. También lo es OUTER en los tres externos, de modo que LEFT JOIN y LEFT OUTER JOIN son la misma sentencia. Y un CROSS JOIN equivale a nombrar las dos tablas separadas por coma en el FROM sin poner condición, que es la forma antigua de escribirlo.

El NATURAL JOIN conviene conocerlo para el examen y evitarlo en producción: como el emparejamiento lo decide el gestor mirando qué columnas comparten nombre, el día que alguien añade a las dos tablas una columna llamada «observaciones» la consulta cambia de significado sin que nadie la haya tocado.

Para el examen

  • INNER JOIN: solo las parejas

  • LEFT, RIGHT y FULL OUTER: conservan las filas sin pareja, rellenando con nulos

  • CROSS JOIN: producto cartesiano, sin ON

  • NATURAL JOIN: empareja por las columnas de igual nombre

Cuántas filas devuelve cada JOIN

La forma segura de fijar los tipos de JOIN es contar filas sobre un caso pequeño. Se parte de tres alumnos y tres intentos, con Ana teniendo dos intentos, Bruno uno y Carla ninguno.

sql
alumno                      intento
id_alumno | nombre          id_intento | id_alumno
--------- | ------          ---------- | ---------
        1 | Ana                    900 |         1
        2 | Bruno                  901 |         1
        3 | Carla                  902 |         2

SELECT a.nombre, i.id_intento
FROM   alumno a
  ...JOIN intento i ON a.id_alumno = i.id_alumno;
JOIN aplicadoFilasQué sale
CROSS9Cada alumno emparejado con cada intento: 3 por 3
INNER3Ana con 900, Ana con 901 y Bruno con 902
LEFT4Las tres anteriores más Carla con id_intento nulo
RIGHT3Las tres anteriores; ningún intento se queda sin alumno
FULL4Las tres coincidentes más Carla con nulos

Ese ejemplo desmonta un error frecuente: se suele decir que un LEFT JOIN devuelve tantas filas como tenga la tabla de la izquierda, y aquí la izquierda tiene tres alumnos y el resultado tiene cuatro filas. Solo se cumple cuando la relación es de uno a uno como mucho. Con una relación de uno a muchos, cada fila de la izquierda se repite tantas veces como parejas encuentre.

Las filas del FULL salen de sumar las del LEFT y las del RIGHT y restar una vez las del INNER, que están contadas dos veces: 4 más 3 menos 3 son 4.

Para el examen

  • Trampa del LEFT JOIN: no devuelve necesariamente tantas filas como la tabla izquierda

  • Por qué: con relación 1:N cada fila izquierda se repite por cada pareja

  • Fórmula del FULL: FULL = LEFT + RIGHT − INNER

UNION, INTERSECT y EXCEPT

Los operadores de conjuntos trabajan de otra manera que el JOIN. El JOIN pega tablas a lo ancho, añadiendo columnas; estos las apilan a lo alto, sumando o restando filas de dos consultas que devuelven la misma forma de resultado.

Por eso exigen compatibilidad: las dos consultas tienen que devolver el mismo número de columnas y tipos compatibles entre sí, columna a columna y en el mismo orden. Los nombres de las columnas no tienen que coincidir, y el resultado hereda los de la primera consulta.

sql
-- Temas que ha tocado Ana o ha tocado Bruno, sin repetir
SELECT id_tema FROM intento WHERE id_alumno = 1
UNION
SELECT id_tema FROM intento WHERE id_alumno = 2;

-- Temas que han tocado los dos
SELECT id_tema FROM intento WHERE id_alumno = 1
INTERSECT
SELECT id_tema FROM intento WHERE id_alumno = 2;

-- Temas que existen y que Ana todavía no ha tocado
SELECT id_tema FROM tema
EXCEPT
SELECT id_tema FROM intento WHERE id_alumno = 1;

UNION elimina las filas duplicadas del resultado; UNION ALL no las elimina y las devuelve todas. Esa diferencia se pregunta mucho y tiene además consecuencia de rendimiento: para quitar duplicados el gestor tiene que ordenar o comparar todo el resultado, así que UNION ALL es más rápido y es el que hay que usar cuando se sabe que no puede haber repeticiones. EXCEPT se llama MINUS en Oracle.

Para el examen

  • UNION frente a UNION ALL: UNION quita duplicados; UNION ALL no, y por eso es más rápido

  • INTERSECT: devuelve lo común

  • EXCEPT: la resta; en Oracle se llama MINUS

  • Requisito de los tres: mismo número de columnas y tipos compatibles

Subconsultas

Una subconsulta es un SELECT escrito dentro de otra sentencia. Lo habitual es verla en el WHERE, pero también puede aparecer en la lista de columnas del SELECT, en el FROM haciendo las veces de tabla, y en el USING de un MERGE. Se clasifican por lo que devuelven y por si dependen o no de la consulta que las envuelve.

  • Escalar: devuelve un único valor, y por eso se puede comparar con un igual, un mayor o un menor.
  • De lista o de fila múltiple: devuelve una columna con varias filas, y entonces la comparación tiene que ser con IN, con ANY, con SOME o con ALL.
  • De tabla: devuelve varias filas y varias columnas, y se usa en el FROM como si fuera una tabla más.
  • No correlacionada: se puede ejecutar sola, porque no menciona nada de la consulta externa. El gestor la resuelve una vez y reutiliza el resultado.
  • Correlacionada: usa una columna de la consulta externa, así que no tiene sentido por separado y conceptualmente se reevalúa para cada fila de la externa.
sql
-- Escalar: la interna devuelve un solo valor
SELECT i.id_intento, i.aciertos
FROM   intento i
WHERE  i.aciertos > (SELECT AVG(aciertos) FROM intento);

-- De lista: se compara con IN
SELECT nombre
FROM   alumno
WHERE  id_alumno IN (SELECT id_alumno FROM intento WHERE fallos > 20);

-- Correlacionada: la interna usa a.id_alumno, de la externa
SELECT a.nombre,
       (SELECT COUNT(*) FROM intento i
        WHERE  i.id_alumno = a.id_alumno) AS intentos
FROM   alumno a;

Si la subconsulta se puede ejecutar por separado y da un resultado con sentido, es no correlacionada. Si al aislarla se queda hablando de una columna que ya no existe, es correlacionada.

Para el examen

  • Escalar: devuelve un valor; se compara con =

  • De lista: varias filas; exige IN, ANY o ALL

  • De tabla: va en el FROM

  • Correlacionada: usa columnas de la consulta externa y no se puede ejecutar sola

EXISTS, IN, ANY y ALL

EXISTS no compara valores: solo pregunta si la subconsulta devuelve alguna fila. Por eso da igual qué se ponga en su SELECT interno, y es costumbre escribir un 1. Es la forma natural de expresar «tiene al menos uno» y su negación, NOT EXISTS, la de expresar «no tiene ninguno».

sql
-- Alumnos que no han hecho ningún intento
SELECT a.nombre
FROM   alumno a
WHERE  NOT EXISTS (SELECT 1 FROM intento i
                   WHERE  i.id_alumno = a.id_alumno);

-- Temas con más peso que ALGUNO de los del bloque 1
SELECT titulo FROM tema
WHERE  peso > ANY (SELECT peso FROM tema WHERE id_bloque = 1);

-- Temas con más peso que TODOS los del bloque 1
SELECT titulo FROM tema
WHERE  peso > ALL (SELECT peso FROM tema WHERE id_bloque = 1);

ANY y SOME son sinónimos y se cumplen si la comparación es cierta para al menos un valor de la lista, así que un mayor que ANY equivale a ser mayor que el mínimo. ALL exige que la comparación sea cierta para todos, de modo que un mayor que ALL equivale a ser mayor que el máximo. Un igual a ANY es lo mismo que un IN.

Queda una trampa que conviene llevar sabida: si la subconsulta de un NOT IN devuelve algún nulo, la condición no se cumple nunca y la consulta no devuelve ninguna fila. La razón es que comparar cualquier cosa con un nulo no da falso, da desconocido, y NOT IN necesita que todas las comparaciones sean falsas. Con NOT EXISTS ese problema no aparece, y por eso se prefiere.

Para el examen

  • EXISTS: pregunta si la subconsulta devuelve alguna fila

  • > ANY y > ALL: superar el mínimo y superar el máximo

  • = ANY: equivale a IN

  • Trampa: un NOT IN con nulos no devuelve ninguna fila