SQL para análise de dados · Guias · 30 de outubro de 2026 · 10 min de leitura

UNION e UNION ALL no SQL: diferença, INTERSECT e EXCEPT, com exemplos

Duas filas de passageiros se juntando numa só no portão de embarque, com cartões de embarque duplicados separados de lado

UNION e UNION ALL empilham os resultados de duas consultas, um embaixo do outro. A única diferença: o UNION remove as linhas duplicadas do resultado combinado, o UNION ALL mantém todas as linhas. Como remover duplicatas dá trabalho extra, o UNION ALL também é mais rápido. A regra prática para analistas: use UNION ALL por padrão, e UNION só quando você realmente quer uma lista de valores distintos. Dois operadores parecidos completam a família: o INTERSECT devolve as linhas presentes nas duas consultas, e o EXCEPT devolve as linhas da primeira consulta que não estão na segunda.

Todos os exemplos rodam no banco de dados da companhia aérea do curso de SQL da Nomto, na sintaxe do PostgreSQL sobre o DuckDB (o motor da sala de SQL da Nomto), e cada tabela de resultado é a saída real.

Um exemplo pequeno para os quatro operadores

A tabela aircrafts tem nove 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

Pegue dois conjuntos que se sobrepõem: os modelos de longo alcance (alcance de pelo menos 5.000 km, cinco modelos) e os modelos da Boeing (três). Dois modelos, o 777 e o 767, estão nos dois.

UNION: tudo, sem duplicatas

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 mais três dá oito, mas os dois Boeings que aparecem nos dois conjuntos ficam uma vez só: seis linhas.

UNION ALL: tudo, com as duplicatas

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

Oito linhas: o UNION ALL simplesmente acrescenta o segundo resultado ao primeiro.

INTERSECT: só as linhas que estão nos dois

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: linhas da primeira consulta que não estão na segunda

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

Diferente dos outros três, o EXCEPT depende da ordem. Inverta as consultas e você recebe os Boeings que não são de longo alcance: só o Boeing 737-300. O Oracle chama esse operador de MINUS; PostgreSQL, DuckDB, SQL Server e MySQL 8.0.31 em diante usam EXCEPT.

O UNION também remove duplicatas dentro de cada consulta

Um engano comum é achar que o UNION só remove as linhas que aparecem nas duas metades. Na verdade, ele remove todas as duplicatas do resultado final, inclusive as repetições dentro de uma mesma metade. Destinos a partir de Sheremetyevo (SVO) e de Pulkovo, em São 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

Ou seja, o UNION se comporta como um UNION ALL seguido de SELECT DISTINCT. INTERSECT e EXCEPT também devolvem linhas distintas por padrão.

Quando o UNION ALL é a resposta certa

Transformar duas colunas em uma antes de contar

Cada voo tem um aeroporto de partida e um de chegada. Para contar todos os movimentos (decolagens mais pousos) por aeroporto, empilhe as duas colunas numa só e depois agrupe:

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

Agora troque UNION ALL por UNION e execute de novo:

airport movements
NUX 1
NFG 1
CSY 1

Todo aeroporto agora tem exatamente um “movimento”. O UNION juntou milhares de linhas idênticas numa só por aeroporto antes de o COUNT vê-las. A consulta roda sem erro e, à primeira vista, os números parecem plausíveis, e é isso que torna esse erro perigoso. Sempre que você empilha linhas para contar ou somar depois, o que você quer é UNION ALL.

Uma tabela-resumo a partir de várias fontes

O UNION ALL é o jeito rápido de colocar números de tabelas diferentes num só resultado:

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

A coluna de rótulo é o que mantém as linhas separadas: mesmo que duas tabelas tivessem a mesma contagem, as linhas seriam diferentes e sobreviveriam também a um UNION. Mas, sem um ORDER BY no fim, o banco não promete devolver as partes na ordem em que você as escreveu. Se a ordem importa, acrescente uma coluna para ordenar.

Acrescentar uma linha de total

Um detalhamento com o total geral embaixo, num só 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 subtotais mais complexos, o GROUP BY ROLLUP faz o mesmo sem repetir a consulta.

Quando EXCEPT e INTERSECT ajudam

O EXCEPT é um jeito compacto de perguntar “o que está faltando?”, uma pergunta que aparece o tempo todo em verificações de dados. Modelos de avião que nunca aparecem na malha:

SELECT aircraft_code FROM aircrafts
EXCEPT
SELECT aircraft_code FROM flights;
aircraft_code
320

É o Airbus A320-200: a companhia o tem na tabela da frota, mas nenhum voo o usa. O mesmo padrão responde perguntas como “membros do programa de fidelidade que nunca compraram um bilhete” (11.426 dos 53.000 aqui) ou “clientes que estavam no arquivo do mês passado, mas não no deste mês”.

O EXCEPT compara linhas inteiras, então funciona com várias colunas ao mesmo tempo. Para verificar se toda rota tem voo de volta, compare os pares (origem, destino) com os pares (destino, origem): SELECT departure_airport, arrival_airport FROM flights EXCEPT SELECT arrival_airport, departure_airport FROM flights. Nesta malha a consulta não devolve nenhuma linha, então toda rota é voada nos dois sentidos.

As mesmas perguntas podem ser escritas com NOT EXISTS ou LEFT JOIN ... IS NULL (veja a parte sobre anti join no guia de JOIN). O EXCEPT é mais curto quando você só precisa das colunas-chave. Quando precisa de outras colunas das linhas que faltam, o NOT EXISTS é mais flexível. Uma diferença para guardar: os operadores de conjunto tratam dois NULL como iguais, enquanto uma condição de JOIN a.x = b.x nunca casa NULL.

As regras para combinar consultas

Todo operador de conjunto exige o mesmo formato dos dois lados:

  1. O mesmo número de colunas. Senão a consulta falha. O DuckDB avisa: Set operations can only apply to expressions with the same number of result columns.
  2. Tipos compatíveis, coluna por coluna. O PostgreSQL recusa SELECT range FROM aircrafts UNION SELECT model FROM aircrafts com “UNION types integer and character varying cannot be matched”. O DuckDB é mais tolerante: converte os números para texto em silêncio e devolve uma coluna misturada (5700, 5600, Boeing 777-300…). Não conte com isso; faça o CAST explícito para que a consulta funcione em qualquer banco.
  3. As colunas são casadas pela posição, não pelo nome. Se a primeira consulta seleciona departure_airport, arrival_airport e a segunda arrival_airport, departure_airport, elas são alinhadas nessa ordem, que é exatamente o que a verificação de rotas de volta acima aproveita, e exatamente o que quebra um relatório em silêncio quando acontece por acidente.
  4. Os nomes das colunas vêm da primeira consulta. SELECT 'x' AS a UNION ALL SELECT 'y' AS b devolve uma coluna chamada a.
  5. ORDER BY e LIMIT no fim valem para o resultado inteiro. Para ordenar ou limitar uma parte só, coloque-a entre parênteses:
(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

Os dois modelos de maior alcance e os dois de menor alcance num só resultado.

Quando você mistura operadores, o INTERSECT tem precedência sobre UNION e EXCEPT no PostgreSQL e no SQL padrão, então a UNION b INTERSECT c significa a UNION (b INTERSECT c). Use parênteses em vez de depender da memória.

INTERSECT ALL e EXCEPT ALL

PostgreSQL e DuckDB também suportam versões ALL de INTERSECT e EXCEPT, que contam as duplicatas em vez de removê-las. Com o lado esquerdo 1, 1, 2:

Elas servem para conciliar duas tabelas linha a linha, por exemplo comparar uma extração da origem com a cópia carregada quando as duplicatas são legítimas. MySQL e SQL Server suportam menos dessas variantes, então confira antes de levar uma consulta para lá.

UNION x JOIN

Os dois combinam duas tabelas, mas em direções diferentes. O UNION acrescenta linhas: o resultado tem a largura de cada consulta e o comprimento das duas juntas. O JOIN acrescenta colunas: linhas de duas tabelas são casadas por uma chave e colocadas lado a lado. Se você quer os voos com o modelo do avião ao lado, é um JOIN; se quer partidas e chegadas numa só lista, é um UNION. Os JOIN estão explicados em detalhe em JOIN no SQL.

Perguntas frequentes

Qual a diferença entre UNION e UNION ALL?

O UNION devolve as linhas combinadas das duas consultas sem duplicatas. O UNION ALL devolve todas as linhas, com as duplicatas. O UNION ALL é mais rápido porque não precisa comparar as linhas.

O UNION ALL é mais rápido que o UNION?

Sim. Para remover duplicatas, o UNION precisa ordenar ou aplicar hash ao resultado inteiro, o que custa tempo e memória em tabelas grandes. O UNION ALL só acrescenta. Se você sabe que as duas partes não podem se sobrepor (por exemplo, têm rótulos diferentes ou vêm de períodos diferentes), o UNION ALL dá as mesmas linhas com menos trabalho.

O UNION remove duplicatas dentro de uma mesma tabela?

Sim. O UNION remove as duplicatas de todo o resultado combinado, inclusive as linhas repetidas dentro de uma das consultas. Funciona como um UNION ALL seguido de DISTINCT.

Posso usar ORDER BY com UNION?

Sim, uma vez, no fim; ele ordena o resultado combinado e pode se referir aos nomes das colunas da primeira consulta ou às posições das colunas. Para ordenar uma parte antes de combinar, coloque essa parte entre parênteses com o próprio ORDER BY e LIMIT.

Qual a diferença entre EXCEPT e NOT IN?

Os dois acham as linhas de um conjunto que faltam em outro. O EXCEPT compara linhas inteiras e trata NULL como iguais. O NOT IN compara uma coluna e não devolve nada se a subconsulta tiver um NULL, uma armadilha clássica. O NOT EXISTS é a escolha geral mais segura; o EXCEPT é o mais curto quando você só precisa das chaves.

Outros idiomas: English, Español, Tiếng Việt, 简体中文