Saltar al contenido
union.sql · devschool

UNION, INTERSECT y EXCEPT en SQL

Lección 13 de 22 · 9 min de lectura · Actualizado el

En esta lección
  1. Las tablas de esta lección
  2. UNION: juntar resultados
  3. UNION ALL: juntar sin quitar duplicados
  4. Las reglas de UNION
  5. INTERSECT: lo que está en las dos
  6. EXCEPT: lo que está en la primera y no en la segunda
  7. ¿UNION u OR?
  8. Errores frecuentes
  9. Resumen

Un JOIN pone tablas una al lado de otra: añade columnas. Los operadores de conjuntos hacen algo distinto: ponen resultados uno debajo de otro, o se quedan con las filas comunes, o con las que sobran. Con UNION, INTERSECT y EXCEPT puedes responder preguntas como “dame todos mis contactos, sean clientes o proveedores” o “¿qué clientes no están suscritos al boletín?”.

Las tablas de esta lección

Además de clientes (ver la introducción), crearemos dos tablas nuevas: los proveedores de la tienda y los suscriptores del boletín (newsletter). Fíjate en que Ana y Pablo son clientes y también están suscritos, e Irene está suscrita pero no es cliente.

CREATE TABLE proveedores (
  id INT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL,
  ciudad VARCHAR(50),
  telefono VARCHAR(15)
);
INSERT INTO proveedores VALUES
  (1, 'Textiles Norte', 'Bilbao', '944000111'),
  (2, 'Cartonajes Pas', 'Torrelavega', '942000222'),
  (3, 'Logística Meseta', 'Madrid', '910000333');

CREATE TABLE newsletter (
  email VARCHAR(100) PRIMARY KEY,
  nombre VARCHAR(100),
  alta DATE
);
INSERT INTO newsletter VALUES
  ('ana@correo.es', 'Ana García', '2026-06-10'),
  ('pablo@correo.es', 'Pablo Díaz', '2026-07-02'),
  ('irene@correo.es', 'Irene Sanz', '2026-08-15');

UNION: juntar resultados

UNION coloca el resultado de la segunda consulta debajo del de la primera y elimina las filas repetidas. ¿En qué ciudades tenemos clientes o proveedores?

SELECT ciudad FROM clientes
UNION
SELECT ciudad FROM proveedores
ORDER BY ciudad;
ciudad
Bilbao
Madrid
Santander
Torrelavega

Hay cinco clientes y tres proveedores, pero salen solo cuatro ciudades: cada una aparece una vez.

UNION ALL: juntar sin quitar duplicados

UNION ALL hace lo mismo, pero conserva todas las filas, repetidas incluidas:

SELECT ciudad FROM clientes
UNION ALL
SELECT ciudad FROM proveedores;
ciudad
Santander
Bilbao
Santander
Madrid
Madrid
Bilbao
Torrelavega
Madrid

Ocho filas: cinco de clientes y tres de proveedores.

¿Cuál uso?

  • UNION necesita comparar todas las filas para quitar las repetidas. Con muchos datos es más lento.
  • UNION ALL solo las pega. Es más rápido.

Regla práctica: usa UNION ALL por defecto y pasa a UNION solo cuando quieras eliminar duplicados. Si sabes que no puede haber repetidos (por ejemplo, porque añades una columna que distingue el origen), UNION ALL da el mismo resultado sin trabajo extra.

Las reglas de UNION

Para que dos consultas se puedan unir:

  1. Deben tener el mismo número de columnas.
  2. Las columnas en la misma posición deben tener tipos compatibles (números con números, textos con textos).
  3. Los nombres de las columnas del resultado son los de la primera consulta.
  4. ORDER BY va una sola vez, al final, y ordena el resultado completo.

Las columnas se emparejan por posición, no por nombre. La primera con la primera, la segunda con la segunda.

Ejemplo: una lista de contactos

Lista única de contactos con una columna que indica de dónde viene cada uno. La columna tipo es un texto fijo que escribimos nosotros:

SELECT nombre, ciudad, 'Cliente' AS tipo FROM clientes
UNION ALL
SELECT nombre, ciudad, 'Proveedor' FROM proveedores
ORDER BY ciudad, nombre;
nombreciudadtipo
Luis PérezBilbaoCliente
Textiles NorteBilbaoProveedor
Eva MartínMadridCliente
Logística MesetaMadridProveedor
Pablo DíazMadridCliente
Ana GarcíaSantanderCliente
Marta RuizSantanderCliente
Cartonajes PasTorrelavegaProveedor

Fíjate en que el alias AS tipo solo se escribe en la primera consulta: es la que da nombre a las columnas. Como las dos partes nunca coinciden (una dice “Cliente” y la otra “Proveedor”), aquí UNION ALL es la elección correcta.

El alias de la primera consulta también es el que usas en el ORDER BY:

SELECT nombre AS contacto FROM clientes WHERE ciudad = 'Madrid'
UNION
SELECT nombre FROM proveedores WHERE ciudad = 'Madrid'
ORDER BY contacto;
contacto
Eva Martín
Logística Meseta
Pablo Díaz

Ejemplo: una línea de tiempo

Con UNION ALL puedes mezclar sucesos de tablas distintas. Cuando una parte no tiene un dato, rellena con NULL:

SELECT fecha, 'Pedido' AS evento, importe FROM pedidos
UNION ALL
SELECT alta, 'Alta newsletter', NULL FROM newsletter
ORDER BY fecha;
fechaeventoimporte
2026-06-10Alta newsletterNULL
2026-07-02Alta newsletterNULL
2026-08-15Alta newsletterNULL
2026-09-01Pedido45.00
2026-09-03Pedido120.50
2026-09-10Pedido12.99
2026-09-12Pedido60.00
2026-09-15Pedido33.25

Ejemplo: un pequeño informe

Varias consultas de una sola fila unidas en una tabla:

SELECT 'Pedidos' AS concepto, COUNT(*) AS total FROM pedidos
UNION ALL
SELECT 'Clientes', COUNT(*) FROM clientes
UNION ALL
SELECT 'Proveedores', COUNT(*) FROM proveedores;
conceptototal
Pedidos5
Clientes5
Proveedores3

INTERSECT: lo que está en las dos

INTERSECT devuelve las filas que aparecen en ambos resultados. ¿Qué clientes están suscritos al boletín?

SELECT nombre FROM clientes
INTERSECT
SELECT nombre FROM newsletter
ORDER BY nombre;
nombre
Ana García
Pablo Díaz

Y las ciudades donde hay a la vez clientes y proveedores:

SELECT ciudad FROM clientes
INTERSECT
SELECT ciudad FROM proveedores
ORDER BY ciudad;
ciudad
Bilbao
Madrid

EXCEPT: lo que está en la primera y no en la segunda

EXCEPT devuelve las filas de la primera consulta que no aparecen en la segunda. Aquí el orden importa: A EXCEPT B no es lo mismo que B EXCEPT A.

-- Clientes que NO están suscritos: candidatos para una campaña
SELECT nombre FROM clientes
EXCEPT
SELECT nombre FROM newsletter
ORDER BY nombre;
nombre
Eva Martín
Luis Pérez
Marta Ruiz
-- Suscriptores que todavía no son clientes
SELECT nombre FROM newsletter
EXCEPT
SELECT nombre FROM clientes;
nombre
Irene Sanz

Igual que UNION, tanto INTERSECT como EXCEPT eliminan duplicados. Algunos gestores tienen también INTERSECT ALL y EXCEPT ALL (PostgreSQL y MySQL), pero SQLite no.

Diferencias entre gestores

GestorUNIONINTERSECTEXCEPT
MySQLSíDesde 8.0.31Desde 8.0.31
MariaDBSíDesde 10.3Desde 10.3
PostgreSQLSíSíSí
SQLiteSíSíSí
OracleSíSíSe llama MINUS (EXCEPT desde 21c)

Si trabajas con un MySQL antiguo, puedes conseguir lo mismo con subconsultas: INTERSECT se parece a WHERE nombre IN (SELECT ...) y EXCEPT a WHERE NOT EXISTS (...). No son idénticos: IN no quita duplicados y la trampa del NULL en NOT IN sigue ahí.

¿UNION u OR?

Si las dos partes leen la misma tabla, a menudo basta con un OR:

-- Clientes de Madrid o menores de edad
SELECT nombre, ciudad FROM clientes
WHERE ciudad = 'Madrid' OR edad < 20;
nombreciudad
Pablo DíazMadrid
Eva MartínMadrid

La versión con UNION ALL sacaría a Pablo dos veces, porque cumple las dos condiciones. Reserva UNION para cuando los datos vienen de tablas distintas o de consultas muy diferentes.

Errores frecuentes

  • Distinto número de columnas.
SELECT nombre, ciudad FROM clientes
UNION
SELECT nombre FROM proveedores;
-- Error: las dos consultas no tienen el mismo número de columnas
  • ORDER BY en medio. Solo puede ir al final. SELECT ... ORDER BY nombre UNION SELECT ... da error. Si necesitas ordenar o limitar una parte por separado, métela entre paréntesis como subconsulta.
  • Columnas desordenadas. Si en la primera consulta pones nombre, ciudad y en la segunda ciudad, nombre, no hay error (las dos son texto), pero las ciudades acaban en la columna nombre. Revisa el orden.
  • Tipos incompatibles. Unir edad (número) con telefono (texto) funciona en MySQL y SQLite, que convierten los valores sin avisar, pero en PostgreSQL da error. Aunque funcione, mezclar tipos en una columna es mala señal.
  • Usar UNION sin querer quitar duplicados. Si dos clientes distintos se llaman igual y viven en la misma ciudad, UNION los fusionaría en uno. Cuando dudes, UNION ALL.

Resumen

OperadorDevuelve¿Quita duplicados?
A UNION BFilas de A y de BSí
A UNION ALL BFilas de A y de BNo (más rápido)
A INTERSECT BFilas que están en A y en BSí
A EXCEPT BFilas de A que no están en BSí
  • Mismo número de columnas, tipos compatibles, emparejadas por posición.
  • Los nombres de las columnas salen de la primera consulta.
  • Un único ORDER BY, al final del todo.
  • FULL OUTER JOIN se emula en MySQL con UNION ALL: lo tienes en más JOIN.

En la siguiente lección, INSERT, UPDATE y DELETE, dejarás de solo leer datos y empezarás a modificarlos.

Pon a prueba lo que has aprendido

[SQL] Con las tablas del curso, ¿cuántas filas devuelve esta consulta?
SELECT ciudad FROM clientes
UNION ALL
SELECT ciudad FROM clientes WHERE edad > 30;

[SQL] ¿Cómo se llama la columna del resultado?
SELECT nombre AS contacto FROM clientes
UNION
SELECT nombre AS proveedor FROM proveedores;

[SQL] Los clientes son Ana, Luis, Marta, Pablo y Eva. En newsletter están Ana, Pablo e Irene. ¿Qué devuelve esta consulta?
SELECT nombre FROM newsletter
EXCEPT
SELECT nombre FROM clientes;

[SQL] Quieres ordenar por nombre el resultado de un UNION. ¿Dónde va el ORDER BY?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.