EXCEPT

El operador EXCEPT devuelve las filas de la primera consulta que no aparecen en la segunda. Es la diferencia de conjuntos: toma el primer resultado y le resta todo lo que también aparece en el segundo.

MySQL soporta EXCEPT desde la versión 8.0.31. En versiones anteriores, puedes conseguir el mismo resultado con LEFT JOIN ... WHERE IS NULL o con NOT IN.

Sintaxis

SELECT columnas FROM tabla1
EXCEPT
SELECT columnas FROM tabla2;

Ambas consultas deben devolver el mismo número de columnas. El orden importa: A EXCEPT B no es lo mismo que B EXCEPT A. La primera devuelve lo que está en A pero no en B, y la segunda lo que está en B pero no en A.

Productos premium que no son más vendidos

Productos con etiqueta "premium" que no tienen la etiqueta "más vendido":

SELECT p.nombre, p.precio
FROM productos p
JOIN etiquetas_producto ep ON p.id = ep.producto_id
WHERE ep.etiqueta = 'premium'
 
EXCEPT
 
SELECT p.nombre, p.precio
FROM productos p
JOIN etiquetas_producto ep ON p.id = ep.producto_id
WHERE ep.etiqueta = 'más vendido';
nombreprecio
MacBook Air M31399.00
ASUS ROG Zephyrus1899.99
Sofá 3 plazas599.00

De los 4 productos premium (iPhone, MacBook, ASUS ROG, Sofá), se elimina el iPhone porque también es "más vendido". Quedan 3 productos que son premium pero no están entre los más vendidos.

Clientes que compraron pero no opinaron

Clientes con pedidos que nunca han dejado una reseña:

SELECT cl.nombre, cl.apellidos
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
 
EXCEPT
 
SELECT cl.nombre, cl.apellidos
FROM clientes cl
JOIN resenas r ON cl.id = r.cliente_id
 
ORDER BY nombre;
nombreapellidos
AlejandroSerrano Blanco
CarmenRuiz Jiménez
ElenaRomero Navarro
FernandoMolina Ortega
MartaBlanco Castro
PaulaOrtega Serrano
RobertoNavarro Gil

Estos 7 clientes han realizado pedidos pero nunca han dejado una reseña. Son candidatos para una campaña que incentive las opiniones después de la compra.

El orden importa

Si invertimos la consulta anterior, obtenemos un resultado completamente diferente: clientes que opinaron pero nunca compraron:

SELECT cl.nombre, cl.apellidos
FROM clientes cl
JOIN resenas r ON cl.id = r.cliente_id
 
EXCEPT
 
SELECT cl.nombre, cl.apellidos
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id;
nombreapellidos
DiegoCastro Iglesias

Diego es el único cliente que ha dejado una reseña sin haber comprado nunca. A EXCEPT B y B EXCEPT A son operaciones distintas con resultados distintos.

Categorías con productos sin ventas

Categorías que tienen productos en el catálogo pero donde ninguno de esos productos se ha vendido:

SELECT DISTINCT c.nombre AS categoria
FROM categorias c
JOIN productos p ON c.id = p.categoria_id
 
EXCEPT
 
SELECT DISTINCT c.nombre
FROM categorias c
JOIN productos p ON c.id = p.categoria_id
JOIN detalle_pedidos dp ON p.id = dp.producto_id;
Empty set (0.00 sec)

El resultado vacío nos dice que todas las categorías con productos tienen al menos una venta. No hay ninguna categoría completamente sin ventas (aunque hay 3 productos individuales que nunca se vendieron, están en categorías donde otros productos sí lo hicieron).

Alternativas para versiones anteriores

En versiones de MySQL anteriores a 8.0.31, puedes emular EXCEPT de dos formas.

Con LEFT JOIN ... IS NULL:

SELECT DISTINCT cl.nombre, cl.apellidos
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
LEFT JOIN resenas r ON cl.id = r.cliente_id
WHERE r.id IS NULL
ORDER BY cl.nombre;

Con NOT IN:

SELECT DISTINCT cl.nombre, cl.apellidos
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
WHERE cl.id NOT IN (
    SELECT cliente_id FROM resenas
)
ORDER BY cl.nombre;

Ambas producen el mismo resultado que el EXCEPT. La versión con LEFT JOIN suele tener mejor rendimiento que NOT IN, especialmente cuando la subconsulta puede contener valores NULL (recuerda que NOT IN con NULLs produce resultados vacíos, como vimos en la sección de operadores).

EXCEPT ALL

EXCEPT ALL conserva los duplicados según su frecuencia. Si una fila aparece 3 veces en la primera consulta y 1 vez en la segunda, EXCEPT ALL la mantiene 2 veces. EXCEPT (sin ALL) la eliminaría completamente:

-- EXCEPT: elimina la fila si aparece en la segunda consulta
-- EXCEPT ALL: resta la frecuencia

En la práctica, EXCEPT ALL se usa rara vez. La versión sin ALL es la más habitual.

Resumen de operaciones de conjunto

Las tres operaciones de conjunto trabajan sobre resultados de consultas completas:

UNION combina ambos resultados eliminando duplicados. UNION ALL combina sin eliminar. INTERSECT devuelve solo lo que está en ambos. EXCEPT devuelve lo que está en el primero pero no en el segundo.

Todas requieren que las consultas tengan el mismo número de columnas. Son especialmente útiles para comparar conjuntos de datos, encontrar diferencias y construir informes consolidados.

Practica con EXCEPT

Usa el editor para encontrar filas presentes en una consulta pero no en otra:

Simulador SQL
Ctrl+Enter para ejecutar

Con esto completamos las operaciones de conjunto. En la siguiente sección exploraremos las subconsultas, una herramienta fundamental que permite anidar consultas dentro de otras consultas.

Escrito por Eduardo Lázaro