UNION, INTERSECT, EXCEPT y CROSS JOIN
En resumen: UNION combina los resultados de dos consultas, INTERSECT conserva las filas comunes, EXCEPT quita de la primera las filas de la segunda, y CROSS JOIN devuelve cada par de filas.
Problema
Una lista de dos lugares
La tienda envía un boletín de otoño a sus clientes activos. Activo es todo el que ha hecho un pedido desde el 1 de septiembre de 2026 o ha escrito una reseña desde el 1 de junio.
Los pedidos y las reseñas están en tablas distintas. Si las unes con JOIN, solo quedan los clientes que tienen las dos cosas. Necesitamos las dos listas, una debajo de la otra.
La idea
UNION
UNION pone el resultado de la segunda consulta debajo del de la primera y quita las repeticiones:
11 clientes. Cada una de las dos consultas está completa, con su propio FROM, JOIN y WHERE. Los nombres de las columnas vienen de la primera.
UNION ALL conserva también las repeticiones:
17 filas: cinco clientes aparecen más de una vez (con varios pedidos, o con un pedido y una reseña). El boletín necesita UNION, o alguien recibirá el correo tres veces. UNION ALL es para cuando cada fila es un suceso aparte y hay que contarla.
Pruébalo
Una cata de café
La tienda invita a una cata a todos los que han comprado café en grano (producto 17) o han escrito una reseña sobre él. Muestra sus nombres, cada uno una vez.
Solución
SELECT c.name FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
WHERE oi.product_id = 17
UNION
SELECT c.name FROM reviews r
JOIN customers c ON c.id = r.customer_id
WHERE r.product_id = 17;Dos consultas con UNION: los compradores a través de orders, customers y order_items con oi.product_id = 17, y los autores de reseñas a través de reviews y customers con r.product_id = 17. Seis personas.
Míralo
Las columnas deben coincidir
Las dos consultas dan filas para una misma tabla, así que deben tener el mismo número de columnas:
Las columnas se emparejan por posición, no por nombre. Si la primera consulta devuelve name, email, la segunda también debe devolver dos columnas, y en un orden con sentido: nombre debajo de nombre.
Para todo el historial de un cliente, añade una columna que diga de dónde viene la fila. Los pedidos y las reseñas de Maria, por fecha:
ORDER BY al final ordena todo el resultado, no solo la segunda consulta. Las columnas se llaman day y event, como en la primera consulta.
La idea
INTERSECT y EXCEPT
INTERSECT conserva las filas que están en los dos resultados. Los productos que se han pedido y además tienen una reseña:
13 productos. Son exactamente los productos con reseña, porque todos los productos se han pedido (lo viste en la lección sobre LEFT JOIN).
EXCEPT conserva las filas del primer resultado que no están en el segundo. Los productos sin reseña:
12 productos. LEFT JOIN reviews … WHERE r.id IS NULL da lo mismo. EXCEPT se lee como una frase: «todos los productos, menos los que tienen una reseña». Con EXCEPT el orden de las consultas importa; con UNION e INTERSECT, no.
Pruébalo
Pedir una reseña
¿Qué productos se han comprado desde el 1 de septiembre de 2026 pero no tienen ninguna reseña? A sus compradores se les pedirá una. Muestra los nombres de los productos.
Solución
SELECT p.name FROM order_items oi
JOIN orders o ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.ordered_at >= '2026-09-01'
EXCEPT
SELECT p.name FROM reviews r
JOIN products p ON p.id = r.product_id;Los comprados desde septiembre (order_items, orders, products con o.ordered_at >= '2026-09-01') EXCEPT los productos con una reseña. 10 productos, entre ellos la tarjeta de regalo y la banitsa.
La idea
CROSS JOIN
A veces necesitas cada par. El informe por ciudad y categoría debe tener una fila para cada ciudad y cada subcategoría, incluso donde no hay ventas. La cuadrícula se hace con CROSS JOIN: cada fila de una tabla con cada fila de la otra, sin ON.
6 ciudades por 6 subcategorías, 36 filas. DISTINCT hace falta porque la ciudad se repite en customers: sin él hay 114 filas, una por cada cliente con ciudad y cada subcategoría.
Rellenar la cuadrícula con las ventas (y los ceros) lo aprenderás en el módulo de subconsultas. La cuadrícula es la primera mitad.
Míralo
Vigila el número de filas
CROSS JOIN multiplica. 60 pedidos y 152 líneas de pedido:
9120 pares, casi todos sin sentido. Lo mismo pasa si olvidas ON. En SQLite un JOIN sin ON es un CROSS JOIN, sin ningún error:
1200 filas (60 × 20) en lugar de 60. PostgreSQL da un error en este caso. Si un informe muestra de pronto muchas más filas o mucho más dinero de lo esperado, revisa primero las condiciones de ON.
Ejercicio
La cuadrícula de las categorías de arriba
El informe anual es por ciudad y categoría de arriba («Alimentos», «Hogar»). Haz la cuadrícula: cada ciudad (sin la vacía) con cada categoría de arriba, cada par una vez.
Solución
SELECT DISTINCT cu.city, cat.name AS category
FROM customers cu
CROSS JOIN categories cat
WHERE cu.city IS NOT NULL AND cat.parent_id IS NULL;FROM customers cu CROSS JOIN categories cat WHERE cu.city IS NOT NULL AND cat.parent_id IS NULL, con DISTINCT. 6 ciudades por 2 categorías, 12 filas. Las categorías de arriba son las que no tienen padre.
Más precisamente, para curiosos
UNION en SQLite a menudo devuelve las filas ordenadas, porque así quita las repeticiones. Eso no está prometido. Siempre que el orden importe, escribe ORDER BY al final.
INTERSECT y EXCEPT existen en SQLite, PostgreSQL y SQL Server. En Oracle EXCEPT se llama MINUS, y las versiones nuevas entienden también EXCEPT. MySQL los tiene desde la versión 8.0.31.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Funciones de agregación: COUNT, SUM, AVG · JOIN: dos tablas en una consulta · LEFT JOIN: también las filas sin pareja · Muchas tablas, y una tabla unida consigo misma
- Continúa en: Un informe por pasos: WITH · Pools de conexiones, copias de seguridad y permisos · Buscar por significado
- Ejercicios sobre el tema: Unir tablas