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

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):
- SVO voa para 48 aeroportos distintos, LED para 24, e 8 deles são atendidos pelos dois.
- O
UNIONdas duas listas dearrival_airportdevolve 64 linhas: 48 + 24 − 8. - O
UNION ALLdas mesmas duas consultas devolve 4.881 linhas, uma por voo, porque cada destino aparece uma vez para cada partida da malha.
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:
- 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. - Tipos compatíveis, coluna por coluna. O PostgreSQL recusa
SELECT range FROM aircrafts UNION SELECT model FROM aircraftscom “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 oCASTexplícito para que a consulta funcione em qualquer banco. - As colunas são casadas pela posição, não pelo nome. Se a primeira consulta seleciona
departure_airport, arrival_airporte a segundaarrival_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. - Os nomes das colunas vêm da primeira consulta.
SELECT 'x' AS a UNION ALL SELECT 'y' AS bdevolve uma coluna chamadaa. ORDER BYeLIMITno 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:
EXCEPT ALLcontra1devolve1, 2: um dos dois 1 é cancelado.INTERSECT ALLcontra1, 1, 1devolve1, 1: tantos quantos o lado menor tiver.
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, 简体中文