SQL · Guías · 30 de octubre de 2026 · 10 min de lectura

UNION y UNION ALL en SQL: diferencia, INTERSECT y EXCEPT con ejemplos

Dos filas de pasajeros que se funden en una en la puerta de embarque, con las tarjetas duplicadas apartadas

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):

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:

  1. 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.
  2. Tipos compatibles, columna a columna. PostgreSQL rechaza SELECT range FROM aircrafts UNION SELECT model FROM aircrafts con 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.
  3. Las columnas se emparejan por posición, no por nombre. Si la primera consulta selecciona departure_airport, arrival_airport y la segunda arrival_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.
  4. Los nombres de las columnas salen de la primera consulta. SELECT 'x' AS a UNION ALL SELECT 'y' AS b devuelve una columna llamada a.
  5. ORDER BY y LIMIT al 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:

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, 简体中文