sqlpostgresqlintersectset-operators

INTERSECT no SQL: linhas presentes em ambas as consultas

Como o operador INTERSECT encontra linhas comuns a duas consultas, como difere do INTERSECT ALL e quando um JOIN ou EXISTS encaixa melhor.

3 min de leituraReferencesql · postgresql · intersect · set-operators · mysql

O operador INTERSECT retorna apenas as linhas que aparecem no resultado das duas consultas ao mesmo tempo. E a forma mais direta de responder "quem esta nos dois conjuntos" sem montar um join na mao, e e um dos tres operadores de conjuntos do SQL ao lado de UNION e EXCEPT.

Sintaxe basica

INTERSECT fica entre duas instrucoes SELECT e compara linhas inteiras, coluna a coluna. As duas consultas precisam retornar o mesmo numero de colunas com tipos compativeis.

-- Users who both placed an order and requested a refund
SELECT user_id FROM purchases
INTERSECT
SELECT user_id FROM refunds;

O resultado e um conjunto ordenado de valores user_id unicos presentes em cada entrada. Um exemplo concreto sobre o nosso esquema: paises que tem ao mesmo tempo usuarios cadastrados e pedidos pagos.

SELECT country FROM users
INTERSECT
SELECT u.country
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';

Propriedades principais do operador:

  • Todas as colunas sao comparadas, e NULL e tratado como igual a NULL (ao contrario de um = comum).
  • As duplicatas sao removidas por padrao, assim como em DISTINCT.
  • A ordem das colunas importa: a primeira casa com a primeira, a segunda com a segunda.

INTERSECT versus INTERSECT ALL

Por padrao o INTERSECT remove duplicatas. Quando voce precisa de uma linha repetida tantas vezes quanto ela aparece nas duas entradas, use INTERSECT ALL: ele mantem o numero minimo de ocorrencias 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';

Se user_id = 7 aparece 3 vezes a esquerda e 2 a direita, o INTERSECT ALL o retorna duas vezes, enquanto o INTERSECT simples o retorna apenas uma vez. Na pratica a forma sem ALL e a mais usada: normalmente importa se uma linha pertence aos dois conjuntos, nao quantas copias existem. E nao trate o INTERSECT ALL como "de graca": ele ainda precisa casar as linhas e contar as ocorrencias de cada lado para manter min(count_left, count_right) copias. A unica economia frente ao INTERSECT simples e que a deduplicacao final e dispensada; o casamento e a contagem continuam acontecendo.

Pegadinha: no PostgreSQL o INTERSECT tem prioridade maior que UNION e EXCEPT. Numa cadeia como A UNION B INTERSECT C, o motor avalia primeiro B INTERSECT C. Use parenteses explicitos para nunca precisar adivinhar.

INTERSECT versus JOIN e EXISTS

A mesma "pertinencia comum" pode ser expressa com um INNER JOIN ou com EXISTS, mas a semantica difere nos detalhes.

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

  • INTERSECT quando voce compara conjuntos de linhas inteiros e quer deduplicacao automatica.
  • EXISTS quando precisa de colunas extras da tabela principal que a segunda consulta nao expoe.
  • INNER JOIN quando precisa de dados das duas tabelas, mas atencao a multiplicacao de linhas e recorra ao DISTINCT.

O tratamento de NULL e a diferenca principal: o INTERSECT considera NULL igual a NULL e casa essas linhas, enquanto um join sobre a.user_id = b.user_id descarta as linhas cuja chave e NULL. O INTERSECT brilha quando os dois conjuntos vem de expressoes complexas: em vez de uma subconsulta desajeitada no WHERE, voce escreve dois blocos SELECT legiveis.

Suporte por banco

O INTERSECT esta disponivel ha muito tempo em PostgreSQL, Oracle, SQL Server e SQLite. No MySQL o operador so chegou na versao 8.0.31 — versoes anteriores o emulavam com um INNER JOIN mais DISTINCT, ou com IN.

-- MySQL < 8.0.31 emulation of INTERSECT
SELECT DISTINCT user_id FROM purchases
WHERE user_id IN (SELECT user_id FROM refunds);

No ClickHouse, o INTERSECT e suportado, e se ele deduplica ou mantem as duplicatas e governado pelo ajuste intersect_default_mode, nao e algo fixo. Para nao depender desse ajuste, escreva INTERSECT DISTINCT para a semantica classica de conjuntos ou INTERSECT ALL quando quiser preservar a multiplicidade.

Resumo rapido:

  • INTERSECT — intersecao de conjuntos com remocao de duplicatas.
  • INTERSECT ALL — intersecao que respeita a multiplicidade: min(count_left, count_right) copias de cada linha.
  • Disponivel no MySQL desde 8.0.31; no ClickHouse o comportamento padrao e controlado por intersect_default_mode, entao escreva DISTINCT ou ALL de forma explicita.

O INTERSECT torna as consultas de "pertinencia comum" declarativas e legiveis — use-o ao comparar conjuntos inteiros de linhas, e reserve EXISTS/JOIN para os casos em que precisa de colunas adicionais.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador