El operador INTERSECT devuelve solo las filas que aparecen en el resultado de ambas consultas a la vez. Es la forma mas directa de responder "quien esta en los dos conjuntos" sin montar un join a mano, y es uno de los tres operadores de conjuntos de SQL junto con UNION y EXCEPT.
Sintaxis basica
INTERSECT se coloca entre dos sentencias SELECT y compara filas completas, columna a columna. Ambas consultas deben devolver el mismo numero de columnas con tipos compatibles.
SELECT user_id FROM purchases
INTERSECT
SELECT user_id FROM refunds;
El resultado es un conjunto ordenado de valores user_id unicos presentes en cada entrada. Un ejemplo concreto sobre nuestro esquema: paises que tienen a la vez usuarios registrados y pedidos pagados.
SELECT country FROM users
INTERSECT
SELECT u.country
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';
Propiedades clave del operador:
- Se comparan todas las columnas y
NULL se considera igual a NULL (a diferencia de un = normal).
- Los duplicados se eliminan por defecto, igual que con
DISTINCT.
- El orden de las columnas importa: la primera se empareja con la primera, la segunda con la segunda.
INTERSECT frente a INTERSECT ALL
Por defecto INTERSECT elimina duplicados. Cuando necesitas una fila repetida tantas veces como aparece en ambas entradas, usa INTERSECT ALL: conserva el numero minimo de ocurrencias de cada lado.
SELECT user_id FROM orders WHERE amount > 100
INTERSECT
SELECT user_id FROM orders WHERE status = 'paid';
SELECT user_id FROM orders WHERE amount > 100
INTERSECT ALL
SELECT user_id FROM orders WHERE status = 'paid';
Si user_id = 7 aparece 3 veces a la izquierda y 2 a la derecha, INTERSECT ALL lo devuelve dos veces, mientras que el INTERSECT simple lo devuelve una sola vez. En la practica la forma sin ALL es la que mas se usa: normalmente te interesa si una fila pertenece a ambos conjuntos, no cuantas copias hay. Y no consideres INTERSECT ALL "gratis": igualmente tiene que emparejar filas y contar ocurrencias en cada lado para conservar min(count_left, count_right) copias. El unico ahorro frente al INTERSECT simple es que se omite la deduplicacion final; el emparejamiento y el conteo siguen ahi.
Trampa: en PostgreSQL INTERSECT tiene mayor prioridad que UNION y EXCEPT. En una cadena como A UNION B INTERSECT C, el motor evalua primero B INTERSECT C. Usa parentesis explicitos para no tener que adivinar.
INTERSECT frente a JOIN y EXISTS
La misma "pertenencia comun" se puede expresar con un INNER JOIN o con EXISTS, pero la semantica difiere en los detalles.
SELECT DISTINCT a.user_id
FROM orders a
JOIN refunds b ON a.user_id = b.user_id;
SELECT DISTINCT o.user_id
FROM orders o
WHERE EXISTS (
SELECT 1 FROM refunds r WHERE r.user_id = o.user_id
);
Como elegir:
INTERSECT cuando comparas conjuntos de filas completos y quieres deduplicacion automatica.
EXISTS cuando necesitas columnas adicionales de la tabla principal que la segunda consulta no expone.
INNER JOIN cuando necesitas datos de ambas tablas, pero cuidado con la multiplicacion de filas y recurre a DISTINCT.
El manejo de NULL es la diferencia clave: INTERSECT considera NULL igual a NULL y empareja esas filas, mientras que un join sobre a.user_id = b.user_id descarta las filas cuya clave es NULL. INTERSECT brilla cuando ambos conjuntos provienen de expresiones complejas: en lugar de una subconsulta torpe en WHERE, escribes dos bloques SELECT legibles.
Soporte por motor
INTERSECT lleva tiempo disponible en PostgreSQL, Oracle, SQL Server y SQLite. En MySQL el operador llego solo en la version 8.0.31 — las versiones anteriores lo emulaban con un INNER JOIN mas DISTINCT, o con IN.
SELECT DISTINCT user_id FROM purchases
WHERE user_id IN (SELECT user_id FROM refunds);
En ClickHouse, INTERSECT esta soportado, y que deduplique o conserve los duplicados lo gobierna el ajuste intersect_default_mode, no es algo fijo. Para no depender de ese ajuste, escribe INTERSECT DISTINCT para la semantica clasica de conjuntos o INTERSECT ALL cuando quieras conservar la multiplicidad.
Resumen rapido:
INTERSECT — interseccion de conjuntos con eliminacion de duplicados.
INTERSECT ALL — interseccion que respeta la multiplicidad: min(count_left, count_right) copias de cada fila.
- Disponible en MySQL desde 8.0.31; en ClickHouse el comportamiento por defecto lo controla
intersect_default_mode, asi que escribe DISTINCT o ALL de forma explicita.
INTERSECT hace que las consultas de "pertenencia comun" sean declarativas y legibles — usalo cuando compares conjuntos enteros de filas, y deja EXISTS/JOIN para los casos en los que necesitas columnas adicionales.
El operador
INTERSECTdevuelve solo las filas que aparecen en el resultado de ambas consultas a la vez. Es la forma mas directa de responder "quien esta en los dos conjuntos" sin montar un join a mano, y es uno de los tres operadores de conjuntos de SQL junto conUNIONyEXCEPT.Sintaxis basica
INTERSECTse coloca entre dos sentenciasSELECTy compara filas completas, columna a columna. Ambas consultas deben devolver el mismo numero de columnas con tipos compatibles.-- Users who both placed an order and requested a refund SELECT user_id FROM purchases INTERSECT SELECT user_id FROM refunds;El resultado es un conjunto ordenado de valores
user_idunicos presentes en cada entrada. Un ejemplo concreto sobre nuestro esquema: paises que tienen a la vez usuarios registrados y pedidos pagados.SELECT country FROM users INTERSECT SELECT u.country FROM users u JOIN orders o ON o.user_id = u.id WHERE o.status = 'paid';Propiedades clave del operador:
NULLse considera igual aNULL(a diferencia de un=normal).DISTINCT.INTERSECT frente a INTERSECT ALL
Por defecto
INTERSECTelimina duplicados. Cuando necesitas una fila repetida tantas veces como aparece en ambas entradas, usaINTERSECT ALL: conserva el numero minimo de ocurrencias de cada lado.-- DISTINCT semantics: each matching id appears once SELECT user_id FROM orders WHERE amount > 100 INTERSECT SELECT user_id FROM orders WHERE status = 'paid'; -- Multiplicity preserved: min(count_left, count_right) copies SELECT user_id FROM orders WHERE amount > 100 INTERSECT ALL SELECT user_id FROM orders WHERE status = 'paid';Si
user_id = 7aparece 3 veces a la izquierda y 2 a la derecha,INTERSECT ALLlo devuelve dos veces, mientras que elINTERSECTsimple lo devuelve una sola vez. En la practica la forma sinALLes la que mas se usa: normalmente te interesa si una fila pertenece a ambos conjuntos, no cuantas copias hay. Y no consideresINTERSECT ALL"gratis": igualmente tiene que emparejar filas y contar ocurrencias en cada lado para conservarmin(count_left, count_right)copias. El unico ahorro frente alINTERSECTsimple es que se omite la deduplicacion final; el emparejamiento y el conteo siguen ahi.INTERSECT frente a JOIN y EXISTS
La misma "pertenencia comun" se puede expresar con un
INNER JOINo conEXISTS, pero la semantica difiere en los detalles.-- INNER JOIN can multiply rows if either side has duplicates SELECT DISTINCT a.user_id FROM orders a JOIN refunds b ON a.user_id = b.user_id; -- EXISTS: clean, no row multiplication, handles NULL via predicate SELECT DISTINCT o.user_id FROM orders o WHERE EXISTS ( SELECT 1 FROM refunds r WHERE r.user_id = o.user_id );Como elegir:
INTERSECTcuando comparas conjuntos de filas completos y quieres deduplicacion automatica.EXISTScuando necesitas columnas adicionales de la tabla principal que la segunda consulta no expone.INNER JOINcuando necesitas datos de ambas tablas, pero cuidado con la multiplicacion de filas y recurre aDISTINCT.El manejo de
NULLes la diferencia clave:INTERSECTconsideraNULLigual aNULLy empareja esas filas, mientras que un join sobrea.user_id = b.user_iddescarta las filas cuya clave esNULL.INTERSECTbrilla cuando ambos conjuntos provienen de expresiones complejas: en lugar de una subconsulta torpe enWHERE, escribes dos bloquesSELECTlegibles.Soporte por motor
INTERSECTlleva tiempo disponible en PostgreSQL, Oracle, SQL Server y SQLite. En MySQL el operador llego solo en la version 8.0.31 — las versiones anteriores lo emulaban con unINNER JOINmasDISTINCT, o conIN.-- MySQL < 8.0.31 emulation of INTERSECT SELECT DISTINCT user_id FROM purchases WHERE user_id IN (SELECT user_id FROM refunds);En ClickHouse,
INTERSECTesta soportado, y que deduplique o conserve los duplicados lo gobierna el ajusteintersect_default_mode, no es algo fijo. Para no depender de ese ajuste, escribeINTERSECT DISTINCTpara la semantica clasica de conjuntos oINTERSECT ALLcuando quieras conservar la multiplicidad.Resumen rapido:
INTERSECT— interseccion de conjuntos con eliminacion de duplicados.INTERSECT ALL— interseccion que respeta la multiplicidad:min(count_left, count_right)copias de cada fila.intersect_default_mode, asi que escribeDISTINCToALLde forma explicita.INTERSECThace que las consultas de "pertenencia comun" sean declarativas y legibles — usalo cuando compares conjuntos enteros de filas, y dejaEXISTS/JOINpara los casos en los que necesitas columnas adicionales.