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.
| Tipo | Qué filas conserva | Necesita ON |
|---|---|---|
| CROSS JOIN | Todas las combinaciones posibles: el producto cartesiano | No, no hay condición que aplicar |
| INNER JOIN | Solo las parejas que cumplen la condición | Sí |
| LEFT OUTER JOIN | Las parejas, y además toda fila de la izquierda sin pareja, con nulos a la derecha | Sí |
| RIGHT OUTER JOIN | Las parejas, y además toda fila de la derecha sin pareja, con nulos a la izquierda | Sí |
| FULL OUTER JOIN | Las parejas y las filas sueltas de las dos tablas, rellenando con nulos por el lado que falte | Sí |
| NATURAL JOIN | Las parejas, emparejando por todas las columnas que se llamen igual en las dos tablas | No, 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.
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 aplicado | Filas | Qué sale |
|---|---|---|
| CROSS | 9 | Cada alumno emparejado con cada intento: 3 por 3 |
| INNER | 3 | Ana con 900, Ana con 901 y Bruno con 902 |
| LEFT | 4 | Las tres anteriores más Carla con id_intento nulo |
| RIGHT | 3 | Las tres anteriores; ningún intento se queda sin alumno |
| FULL | 4 | Las 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.
-- 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.
-- 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».
-- 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