SQL · Guías · 30 de octubre de 2026 · 10 min de lectura
UNION y UNION ALL en SQL: diferencia, INTERSECT y EXCEPT con ejemplos

UNION y UNION ALL apilan los resultados de dos consultas uno encima del otro. La única diferencia: UNION elimina las filas duplicadas del resultado combinado y UNION ALL conserva todas las filas. Como quitar duplicados supone trabajo extra, UNION ALL además es más rápido. La regla práctica para analistas: usa UNION ALL por defecto y UNION solo cuando de verdad quieras una lista de valores distintos. Dos operadores más completan la familia: INTERSECT devuelve las filas que están en las dos consultas y EXCEPT devuelve las filas de la primera consulta que faltan en la segunda.
Todos los ejemplos usan la base de datos de una aerolínea del curso de SQL de Nomto, con sintaxis de PostgreSQL sobre DuckDB (el motor de la sala de SQL de Nomto), y cada tabla de resultados es la salida real (valores en inglés, dinero en dólares).
Un ejemplo pequeño para los cuatro operadores
La tabla aircrafts tiene nueve modelos:
| model | range |
|---|---|
| Boeing 777-300 | 11100 |
| Boeing 767-300 | 7900 |
| Airbus A319-100 | 6700 |
| Airbus A320-200 | 5700 |
| Airbus A321-200 | 5600 |
| Boeing 737-300 | 4200 |
| Sukhoi Superjet-100 | 3000 |
| Bombardier CRJ-200 | 2700 |
| Cessna 208 Caravan | 1200 |
Tomemos dos conjuntos que se solapan: los modelos de largo alcance (al menos 5.000 km, cinco modelos) y los modelos de Boeing (tres). Dos modelos, el 777 y el 767, están en ambos.
UNION: todo, sin duplicados
SELECT model FROM aircrafts WHERE range >= 5000
UNION
SELECT model FROM aircrafts WHERE model LIKE 'Boeing%'
ORDER BY model;
| model |
|---|
| Airbus A319-100 |
| Airbus A320-200 |
| Airbus A321-200 |
| Boeing 737-300 |
| Boeing 767-300 |
| Boeing 777-300 |
Cinco más tres son ocho, pero los dos Boeing que aparecen en los dos conjuntos se quedan una sola vez: seis filas.
UNION ALL: todo, duplicados incluidos
SELECT model FROM aircrafts WHERE range >= 5000
UNION ALL
SELECT model FROM aircrafts WHERE model LIKE 'Boeing%'
ORDER BY model;
| model |
|---|
| Airbus A319-100 |
| Airbus A320-200 |
| Airbus A321-200 |
| Boeing 737-300 |
| Boeing 767-300 |
| Boeing 767-300 |
| Boeing 777-300 |
| Boeing 777-300 |
Ocho filas: UNION ALL se limita a añadir el segundo resultado detrás del primero.
INTERSECT: solo las filas que están en los dos
SELECT model FROM aircrafts WHERE range >= 5000
INTERSECT
SELECT model FROM aircrafts WHERE model LIKE 'Boeing%'
ORDER BY model;
| model |
|---|
| Boeing 767-300 |
| Boeing 777-300 |
EXCEPT: filas del primero que no están en el segundo
SELECT model FROM aircrafts WHERE range >= 5000
EXCEPT
SELECT model FROM aircrafts WHERE model LIKE 'Boeing%'
ORDER BY model;
| model |
|---|
| Airbus A319-100 |
| Airbus A320-200 |
| Airbus A321-200 |
A diferencia de los otros tres, en EXCEPT el orden importa. Si intercambias las consultas, obtienes los Boeing que no son de largo alcance: solo el Boeing 737-300. Oracle llama a este operador MINUS; PostgreSQL, DuckDB, SQL Server y MySQL 8.0.31 o posterior usan EXCEPT.
UNION también quita los duplicados dentro de cada consulta
Un malentendido habitual es pensar que UNION solo elimina las filas que aparecen en las dos mitades. En realidad elimina todos los duplicados del resultado final, incluidas las repeticiones dentro de una misma mitad. Destinos desde Sheremétievo (SVO) y desde Púlkovo, en San Petersburgo (LED):
- Desde SVO se vuela a 48 aeropuertos distintos, desde LED a 24, y 8 de ellos se sirven desde los dos.
UNIONde las dos listas dearrival_airportdevuelve 64 filas: 48 + 24 − 8.UNION ALLde las mismas dos consultas devuelve 4.881 filas, una por vuelo, porque cada destino aparece una vez por cada salida del calendario.
SELECT arrival_airport FROM flights WHERE departure_airport = 'SVO'
INTERSECT
SELECT arrival_airport FROM flights WHERE departure_airport = 'LED'
ORDER BY 1;
| arrival_airport |
|---|
| CSY |
| ESL |
| GOJ |
| KVX |
| NOJ |
| OVS |
| TJM |
| VKT |
Es decir, UNION se comporta como UNION ALL seguido de SELECT DISTINCT. INTERSECT y EXCEPT también devuelven filas distintas por defecto.
Cuándo UNION ALL es la respuesta correcta
Convertir dos columnas en una antes de contar
Cada vuelo tiene un aeropuerto de salida y otro de llegada. Para contar todos los movimientos (despegues más aterrizajes) por aeropuerto, apila las dos columnas en una y luego agrupa:
SELECT airport, COUNT(*) AS movements
FROM (
SELECT departure_airport AS airport FROM flights
UNION ALL
SELECT arrival_airport FROM flights
) m
GROUP BY airport
ORDER BY movements DESC
LIMIT 5;
| airport | movements |
|---|---|
| DME | 6434 |
| SVO | 5963 |
| LED | 3802 |
| VKO | 3436 |
| OVB | 2110 |
Ahora cambia UNION ALL por UNION y vuelve a ejecutarla:
| airport | movements |
|---|---|
| LED | 1 |
| VOZ | 1 |
| KXK | 1 |
Todos los aeropuertos tienen ahora exactamente un «movimiento» (y, como empatan, el orden de las filas puede cambiar de una ejecución a otra). UNION ha colapsado miles de filas idénticas en una por aeropuerto antes de que COUNT llegara a verlas. La consulta se ejecuta sin errores y a primera vista los números parecen plausibles, y eso es justo lo que hace peligroso este fallo. Siempre que apiles filas para contarlas o sumarlas después, lo que quieres es UNION ALL.
Una tabla resumen a partir de varias fuentes
UNION ALL es la forma rápida de reunir en un resultado números de tablas distintas:
SELECT 'airports' AS metric, COUNT(*) AS value FROM airports
UNION ALL
SELECT 'flights', COUNT(*) FROM flights
UNION ALL
SELECT 'bookings', COUNT(*) FROM bookings
UNION ALL
SELECT 'tickets', COUNT(*) FROM tickets;
| metric | value |
|---|---|
| airports | 104 |
| flights | 33121 |
| bookings | 262788 |
| tickets | 366733 |
La columna de etiqueta es lo que mantiene separadas las filas: aunque dos tablas tuvieran el mismo recuento, las filas serían distintas y sobrevivirían también a un UNION. Eso sí, sin un ORDER BY final la base de datos no promete devolver las partes en el orden en que las escribiste. Si el orden importa, añade una columna para ordenar.
Añadir una fila de total
Un desglose con el total general debajo, en un solo resultado:
SELECT fare_conditions, COUNT(*) AS segments, ROUND(SUM(amount)) AS revenue
FROM ticket_flights
GROUP BY fare_conditions
UNION ALL
SELECT 'Total', COUNT(*), ROUND(SUM(amount))
FROM ticket_flights
ORDER BY segments;
| fare_conditions | segments | revenue |
|---|---|---|
| Comfort | 17291 | 5661169 |
| Business | 107642 | 55051796 |
| Economy | 920793 | 146956844 |
| Total | 1045726 | 207669809 |
Para subtotales más complejos, GROUP BY ROLLUP hace lo mismo sin repetir la consulta.
Cuándo ayudan EXCEPT e INTERSECT
EXCEPT es una forma compacta de preguntar «¿qué falta?», algo que sale constantemente al revisar datos. Los modelos de avión que no aparecen nunca en el calendario:
SELECT aircraft_code FROM aircrafts
EXCEPT
SELECT aircraft_code FROM flights;
| aircraft_code |
|---|
| 320 |
Es el Airbus A320-200: la aerolínea lo tiene en la tabla de flota, pero ningún vuelo lo usa. El mismo patrón responde a preguntas como «socios del programa de fidelización que nunca han comprado un billete» (aquí, 11.426 de 53.000) o «clientes que estaban en el fichero del mes pasado pero no en el de este mes».
EXCEPT compara filas completas, así que funciona con varias columnas a la vez. Para comprobar si cada ruta tiene vuelo de vuelta, compara los pares (origen, destino) con los pares (destino, origen): SELECT departure_airport, arrival_airport FROM flights EXCEPT SELECT arrival_airport, departure_airport FROM flights. En este calendario no devuelve ninguna fila, así que todas las rutas se vuelan en los dos sentidos.
Las mismas preguntas se pueden escribir con NOT EXISTS o con LEFT JOIN ... IS NULL (lo explica la sección de anti join de la guía de JOIN). EXCEPT es más corto cuando solo necesitas las columnas clave. Cuando necesitas otras columnas de las filas que faltan, NOT EXISTS es más flexible. Una diferencia que conviene recordar: los operadores de conjuntos tratan dos NULL como iguales, mientras que una condición de JOIN a.x = b.x nunca empareja NULL.
Las reglas para combinar consultas
Todos los operadores de conjuntos exigen la misma forma a los dos lados:
- El mismo número de columnas. Si no, la consulta falla. DuckDB lo dice así:
Set operations can only apply to expressions with the same number of result columns. - Tipos compatibles, columna a columna. PostgreSQL rechaza
SELECT range FROM aircrafts UNION SELECT model FROM aircraftscon el error «UNION types integer and character varying cannot be matched». DuckDB es más permisivo: convierte en silencio los números a texto y devuelve una columna mezclada (5600,Boeing 777-300,3000…). No te fíes de eso: convierte los tipos explícitamente para que la consulta funcione en cualquier sitio. - Las columnas se emparejan por posición, no por nombre. Si la primera consulta selecciona
departure_airport, arrival_airporty la segundaarrival_airport, departure_airport, se alinean en ese orden. Es justo lo que aprovecha la comprobación de rutas de vuelta de arriba, y justo lo que rompe un informe sin avisar cuando se hace por accidente. - Los nombres de las columnas salen de la primera consulta.
SELECT 'x' AS a UNION ALL SELECT 'y' AS bdevuelve una columna llamadaa. ORDER BYyLIMITal final se aplican a todo el resultado. Para ordenar o limitar una sola parte, ponla entre paréntesis:
(SELECT model, range FROM aircrafts ORDER BY range DESC LIMIT 2)
UNION ALL
(SELECT model, range FROM aircrafts ORDER BY range LIMIT 2);
| model | range |
|---|---|
| Boeing 777-300 | 11100 |
| Boeing 767-300 | 7900 |
| Cessna 208 Caravan | 1200 |
| Bombardier CRJ-200 | 2700 |
Los dos modelos de mayor alcance y los dos de menor alcance en un solo resultado.
Cuando mezclas operadores, en PostgreSQL y en SQL estándar INTERSECT tiene más prioridad que UNION y EXCEPT, así que a UNION b INTERSECT c significa a UNION (b INTERSECT c). Mejor usa paréntesis que fiarte de la memoria.
INTERSECT ALL y EXCEPT ALL
PostgreSQL y DuckDB también admiten versiones ALL de INTERSECT y EXCEPT, que cuentan los duplicados en lugar de eliminarlos. Con un lado izquierdo 1, 1, 2:
EXCEPT ALLcontra1devuelve1, 2: solo se cancela uno de los dos 1.INTERSECT ALLcontra1, 1, 1devuelve1, 1: tantos como tenga el lado más pequeño.
Sirven para cuadrar dos tablas fila a fila, por ejemplo para comparar una extracción de origen con la copia cargada cuando los duplicados son legítimos. MySQL y SQL Server admiten menos de estas variantes, así que compruébalo antes de llevar una consulta de un motor a otro.
UNION frente a JOIN
Los dos combinan dos tablas, pero en direcciones distintas. UNION añade filas: el resultado es tan ancho como cada consulta y tan largo como las dos juntas. JOIN añade columnas: las filas de dos tablas se emparejan por una clave y se colocan una al lado de la otra. Si quieres los vuelos con el modelo de avión al lado, eso es un JOIN; si quieres salidas y llegadas en una sola lista, eso es un UNION. Los JOIN se explican a fondo en JOIN en SQL.
Preguntas frecuentes
¿Qué diferencia hay entre UNION y UNION ALL?
UNION devuelve las filas combinadas de las dos consultas sin duplicados. UNION ALL devuelve todas las filas, duplicados incluidos. UNION ALL es más rápido porque no necesita comparar filas.
¿UNION ALL es más rápido que UNION?
Sí. Para quitar duplicados, UNION tiene que ordenar o aplicar un hash a todo el resultado, lo que cuesta tiempo y memoria en tablas grandes. UNION ALL simplemente añade. Si sabes que las dos partes no pueden solaparse (por ejemplo, porque tienen etiquetas distintas o vienen de rangos de fechas diferentes), UNION ALL da las mismas filas con menos trabajo.
¿UNION elimina duplicados dentro de una misma tabla?
Sí. UNION elimina los duplicados de todo el resultado combinado, incluidas las filas repetidas dentro de una de las consultas. Funciona como UNION ALL seguido de DISTINCT.
¿Se puede usar ORDER BY con UNION?
Sí, una vez y al final: ordena el resultado combinado y puede referirse a los nombres de columna de la primera consulta o a las posiciones de las columnas. Para ordenar una parte antes de combinar, envuélvela entre paréntesis con su propio ORDER BY y LIMIT.
¿Qué diferencia hay entre EXCEPT y NOT IN?
Los dos buscan filas de un conjunto que faltan en otro. EXCEPT compara filas completas y trata los NULL como iguales. NOT IN compara una columna y no devuelve nada en absoluto si la subconsulta contiene un NULL, una trampa clásica. NOT EXISTS es la opción general más segura; EXCEPT es la más corta cuando solo necesitas las claves.
También en English, Português (Brasil), Tiếng Việt, 简体中文