SQL UNION ALL

Na aula anterior, o UNION juntou duas listas e riscou as repetições. Às vezes, riscar é exatamente o que você não quer. Se a Ana quer saber quantos contatos a loja tem em cada cidade, cada cliente e cada fornecedor tem de contar, mesmo que vários morem em Teresina. O UNION ALL ("união de tudo") empilha as consultas sem tirar nada.

Você vai aprender
  • A diferença entre UNION e UNION ALL, com os mesmos dados lado a lado.
  • Por que contar e somar em cima de um UNION dá números errados, sem erro nenhum.
  • Como pôr uma linha de TOTAL no fim de um relatório.
  • Por que o UNION ALL dá menos trabalho ao banco.
  • O que acontece quando você mistura UNION e UNION ALL no mesmo comando.
  • Os erros reais do SQLite com UNION ALL.

As regras de colunas são as mesmas do UNION: o mesmo número de colunas em todas as consultas, encaixadas pela posição, com os nomes da primeira consulta e o ORDER BY só no fim. Se ainda não leu, comece pela aula UNION.


A ideia: a lista de convidados × a lista de compras

Na lista de convidados da festa, ninguém precisa aparecer duas vezes: um convite por pessoa. Isso é o UNION.

Agora pense na lista de compras. A Ana anotou "2 pacotes de café" e o irmão, na lista dele, também anotou "2 pacotes de café". Se ela juntar as duas listas riscando o repetido, vai comprar 2 pacotes em vez de 4. Na lista de compras, cada linha conta. Isso é o UNION ALL.

UNIONUNION ALL
Linhas repetidasTiraMantém
Número de linhasMenor ou igual à soma das partesSempre a soma exata das partes
Trabalho do bancoPrecisa comparar as linhas para achar as repetidasSó empilha
Na vida realLista de convidadosLista de compras

1. Os mesmos dados, com e sem ALL

A consulta da aula anterior, agora com ALL:

SELECT cidade FROM clientes
UNION ALL
SELECT cidade FROM fornecedores;
UNION ALL
"Empilhe o resultado da próxima consulta embaixo deste, do jeito que vier." São duas palavras, nessa ordem: o ALL vem logo depois do UNION.

Agora são 36 linhas: as 30 cidades dos 30 clientes, seguidas das 6 cidades dos 6 fornecedores. Teresina aparece 12 vezes. Com UNION, a mesma consulta dava 10 linhas, uma por cidade.

Repare também na ordem: o UNION ALL não mexeu em nada, e as linhas dos clientes vieram primeiro, na ordem em que estão na tabela, e as dos fornecedores depois. Mesmo assim, se a ordem importa, peça com ORDER BY no fim: nenhum banco promete ordem sem ele.


2. Contando: aqui o ALL faz falta

A Ana quer saber quantos contatos (clientes e fornecedores) a loja tem em cada cidade. A ideia é juntar as duas listas de cidades e contar com GROUP BY (aula GROUP BY). A lista unida vai entre parênteses, no lugar de uma tabela, no FROM:

SELECT cidade, COUNT(*) AS contatos
FROM (
  SELECT cidade FROM clientes
  UNION ALL
  SELECT cidade FROM fornecedores
)
GROUP BY cidade
ORDER BY contatos DESC, cidade;
FROM ( … )
O resultado do UNION ALL vira uma "tabela" temporária, de onde a consulta de fora lê. Uma consulta dentro de outra se chama subconsulta e tem aula própria: Subconsultas.
GROUP BY cidade
Agrupa as 36 linhas por cidade.
COUNT(*) AS contatos
Conta quantas linhas caíram em cada grupo.

Teresina tem 12 contatos (10 clientes e 2 fornecedores), Fortaleza e São Paulo têm 8 cada uma. A soma da coluna dá 36, um para cada pessoa ou empresa. Agora troque o UNION ALL por UNION e veja o estrago:

SELECT cidade, COUNT(*) AS contatos
FROM (
  SELECT cidade FROM clientes
  UNION
  SELECT cidade FROM fornecedores
)
GROUP BY cidade
ORDER BY contatos DESC, cidade;

Todas as cidades com 1 contato. O UNION deixou uma linha por cidade antes de o COUNT contar, e o banco não deu nenhum aviso. A consulta está "certa" para o SQL e errada para a Ana.

Missão: a consulta abaixo diz que Teresina tem 1 contato só, e a loja tem 12 lá. Conserte-a para que cada cliente e cada fornecedor seja contado.

SELECT cidade, COUNT(*) AS contatos
FROM (
  SELECT cidade FROM clientes
  UNION
  SELECT cidade FROM fornecedores
)
GROUP BY cidade
ORDER BY contatos DESC;
SELECT cidade, COUNT(*) AS contatos
FROM (
  SELECT cidade FROM clientes
  UNION ALL
  SELECT cidade FROM fornecedores
)
GROUP BY cidade
ORDER BY contatos DESC;

3. Somando: o frete que sumiu

O mesmo perigo vale para SUM (aula SUM()). A Ana quer o total de frete cobrado em 2024 e em 2025. Ela juntou os fretes dos dois anos e somou:

SELECT SUM(frete) AS total_frete
FROM (
  SELECT frete FROM pedidos WHERE data LIKE '2024%'
  UNION
  SELECT frete FROM pedidos WHERE data LIKE '2025%'
);

Deu 116.25. Parece pouco para 64 pedidos, e é: a loja cobra poucos valores de frete diferentes (9.9, 15.5, 0.0…), e o UNION deixou um de cada. Sobraram só 8 linhas para somar. Com UNION ALL, cada pedido conta:

SELECT SUM(frete) AS total_frete
FROM (
  SELECT frete FROM pedidos WHERE data LIKE '2024%'
  UNION ALL
  SELECT frete FROM pedidos WHERE data LIKE '2025%'
);

930.0, o valor certo. Para conferir, a mesma soma direto na tabela, sem UNION nenhum:

SELECT SUM(frete) AS total_frete
FROM pedidos
WHERE data < '2026';
Regra de ouro

Se o resultado de um UNION vai ser contado ou somado, ele quase sempre tem de ser UNION ALL. O UNION compara as linhas pelo valor; ele não sabe que dois fretes de 9.9 são de pedidos diferentes.


4. A linha de TOTAL no fim do relatório

Um uso clássico do UNION ALL: o relatório com uma linha por categoria e, embaixo, uma linha de total. A primeira consulta faz as linhas de cada categoria (com INNER JOIN e GROUP BY); a segunda faz uma linha só, com a soma de tudo:

SELECT c.nome AS categoria, SUM(p.estoque) AS estoque
FROM produtos AS p
INNER JOIN categorias AS c ON c.id = p.categoria_id
GROUP BY c.id, c.nome
UNION ALL
SELECT 'TOTAL', SUM(estoque)
FROM produtos;

Oito categorias e o TOTAL de 3301 unidades em estoque. As duas consultas têm 2 colunas: na segunda, o texto fixo 'TOTAL' ocupa o lugar do nome da categoria. Por que ALL aqui? Porque não existe repetição possível, e o UNION ALL poupa o banco de procurar repetições à toa.

Se você quiser ordenar as categorias, cuidado: um ORDER BY estoque DESC no fim jogaria o TOTAL lá para cima, porque ele é o maior número. O truque é uma coluna a mais só para a ordem:

SELECT 1 AS parte, c.nome AS categoria, SUM(p.estoque) AS estoque
FROM produtos AS p
INNER JOIN categorias AS c ON c.id = p.categoria_id
GROUP BY c.id, c.nome
UNION ALL
SELECT 2, 'TOTAL', SUM(estoque)
FROM produtos
ORDER BY parte, estoque DESC;

Primeiro todas as linhas da parte 1 (as categorias, da maior para a menor), depois a parte 2 (o total).


5. UNION ALL dá menos trabalho ao banco

Para tirar repetições, o UNION precisa guardar as linhas e compará-las. O UNION ALL só empilha. Dá para ver isso no plano que o SQLite monta antes de rodar, com EXPLAIN QUERY PLAN (aula EXPLAIN QUERY PLAN, no Nível 3). Compare a penúltima linha de cada um:

EXPLAIN QUERY PLAN
SELECT cidade FROM clientes
UNION
SELECT cidade FROM fornecedores;
EXPLAIN QUERY PLAN
SELECT cidade FROM clientes
UNION ALL
SELECT cidade FROM fornecedores;

Com UNION, aparece UNION USING TEMP B-TREE: o SQLite monta uma estrutura temporária (uma "árvore B") para descobrir as repetidas. Com UNION ALL, só UNION ALL. No terminal do sqlite3, o mesmo plano aparece desenhado como uma árvore, em vez de tabela:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> EXPLAIN QUERY PLAN SELECT cidade FROM clientes UNION SELECT cidade FROM fornecedores;
QUERY PLAN
`--COMPOUND QUERY
   |--LEFT-MOST SUBQUERY
   |  `--SCAN clientes
   `--UNION USING TEMP B-TREE
      `--SCAN fornecedores

Com 36 linhas, a diferença não se sente; com milhões, sente. Por isso, muita gente experiente escreve UNION ALL por padrão e só usa UNION quando precisa mesmo tirar repetições.


6. Qual usar

SituaçãoUsePor quê
Uma lista de nomes ou cidades, cada um uma vezUNIONVocê quer as repetições fora
O resultado vai ser contado (COUNT) ou somado (SUM, AVG)UNION ALLCada linha tem de contar
Linha de TOTAL, contagem de cada tabela, partes que nunca se repetemUNION ALLNão há o que tirar, e ele é mais leve
Uma lista com coluna de origem ('cliente', 'fornecedor')Qualquer umA coluna de origem já deixa as linhas diferentes; UNION ALL é mais leve

Mais um exemplo do terceiro caso, um "raio X" do banco com o número de linhas de cada tabela:

SELECT 'clientes' AS tabela, COUNT(*) AS linhas FROM clientes
UNION ALL
SELECT 'produtos', COUNT(*) FROM produtos
UNION ALL
SELECT 'pedidos', COUNT(*) FROM pedidos;

Quando dá errado

Contou ou somou em cima de um UNION

É o erro das seções 2 e 3, e o mais perigoso, porque não aparece mensagem nenhuma: as cidades com 1 contato cada, o frete de 116.25 em vez de 930.0.

Como perceber: desconfie de números pequenos demais e confira por outro caminho (como o SUM direto na tabela da seção 3).

Como consertar: UNION ALL.

Número de colunas diferente
SELECT nome FROM clientes
UNION ALL
SELECT nome, cidade FROM fornecedores;

A mensagem: SELECTs to the left and right of UNION ALL do not have the same number of result columns, a mesma do UNION, agora com o nome UNION ALL: "os SELECTs à esquerda e à direita do UNION ALL não têm o mesmo número de colunas".

Como consertar: o mesmo número de colunas em todas as consultas.

ORDER BY no meio
SELECT nome FROM clientes
ORDER BY nome
UNION ALL
SELECT nome FROM fornecedores;

A mensagem: ORDER BY clause should come after UNION ALL not before, "a cláusula ORDER BY deve vir depois do UNION ALL, não antes".

Como consertar: um ORDER BY só, depois da última consulta.

Misturou UNION ALL e UNION

A Ana começou com UNION ALL e, no fim, acrescentou mais uma parte com UNION:

SELECT cidade FROM clientes
UNION ALL
SELECT cidade FROM fornecedores
UNION
SELECT 'Teresina';

O que acontece: as 36 linhas viraram 10. O SQLite resolve os operadores da esquerda para a direita: primeiro empilhou tudo (36 linhas), depois aplicou o UNION com a última parte, e esse UNION tirou as repetições do resultado inteiro, não só da última parte. Um único UNION no fim desfaz todos os ALL antes dele.

Como consertar: não misture. Se é para manter as repetições, use UNION ALL em todas as junções.

UNION DISTINCT

Em alguns tutoriais você vai ver UNION DISTINCT, para deixar explícito que as repetições saem.

SELECT cidade FROM clientes
UNION DISTINCT
SELECT cidade FROM fornecedores;

A mensagem: near "DISTINCT": syntax error. O SQLite não conhece essa escrita.

Como consertar: escreva só UNION: ele já tira as repetições.

Nos outros bancos

O UNION ALL é igual no MySQL, no PostgreSQL e no SQL Server: mantém as repetições, com as mesmas regras de colunas. O MySQL e o PostgreSQL aceitam também UNION DISTINCT, que é o mesmo que UNION. Sobre a mistura dos dois, o manual do MySQL diz o mesmo que você viu acima: um UNION (sem ALL) vale por cima de todos os UNION ALL à esquerda dele.


Referência rápida

EscritaO que faz
SELECT … UNION ALL SELECT …Empilha os resultados sem tirar nada; o total de linhas é a soma das partes
SELECT … FROM (… UNION ALL …) GROUP BY …Conta ou soma em cima da lista unida
… UNION ALL SELECT 'TOTAL', SUM(…) FROM …Linha de total no fim do relatório
SELECT 1 AS parte, … UNION ALL SELECT 2, … ORDER BY parteMantém o total por último ao ordenar
… UNION ALL … UNION …Cuidado: o UNION do fim tira as repetições de tudo
Mensagem do SQLiteQuer dizer
SELECTs to the left and right of UNION ALL do not have the same number of result columnsAs consultas têm números de colunas diferentes
ORDER BY clause should come after UNION ALL not beforeO ORDER BY está no meio; mova para o fim
near "DISTINCT": syntax errorUNION DISTINCT não existe no SQLite; use UNION

Exercícios

1. Sem tirar nada

Complete para empilhar as cidades mantendo as repetições:

SELECT cidade FROM clientes UNION SELECT cidade FROM fornecedores;

2. Quantas linhas?

A loja tem 30 clientes e 6 fornecedores. Quantas linhas saem de SELECT cidade FROM clientes UNION ALL SELECT cidade FROM fornecedores?

linhas

3. A Ana somou os fretes de 2024 e de 2025 com SUM em cima de um UNION e achou o valor baixo. Por quê?

4. Qual é a vantagem do UNION ALL quando as partes nunca se repetem (como uma linha de TOTAL)?

5. Quantas linhas saem de SELECT cidade FROM clientes UNION ALL SELECT cidade FROM fornecedores UNION SELECT 'Teresina'?

Missão final: o relatório com total

Missão: o relatório abaixo mostra o estoque de cada categoria. A Ana quer, embaixo, uma linha a mais com o texto TOTAL e a soma do estoque de todos os produtos. Acrescente essa linha com UNION ALL.

SELECT c.nome AS categoria, SUM(p.estoque) AS estoque
FROM produtos AS p
INNER JOIN categorias AS c ON c.id = p.categoria_id
GROUP BY c.id, c.nome;
SELECT c.nome AS categoria, SUM(p.estoque) AS estoque
FROM produtos AS p
INNER JOIN categorias AS c ON c.id = p.categoria_id
GROUP BY c.id, c.nome
UNION ALL
SELECT 'TOTAL', SUM(estoque)
FROM produtos;

Livre: complete o "raio X" do banco com as tabelas que faltam (itens_pedido, funcionarios, categorias, fornecedores), uma linha para cada, com UNION ALL.

SELECT 'clientes' AS tabela, COUNT(*) AS linhas FROM clientes
UNION ALL
SELECT 'produtos', COUNT(*) FROM produtos
UNION ALL
SELECT 'pedidos', COUNT(*) FROM pedidos;

Resumo

  • UNION ALL empilha os resultados sem tirar as repetições: o total de linhas é sempre a soma das partes.
  • Use UNION ALL sempre que o resultado for contado ou somado. Com UNION, valores iguais de linhas diferentes somem e o número sai errado, sem aviso.
  • Ele é ótimo para linhas de TOTAL e para juntar partes que nunca se repetem; e dá menos trabalho ao banco, porque não procura repetições.
  • As regras de colunas e do ORDER BY são as mesmas do UNION.
  • Não misture: um UNION no fim tira as repetições do resultado inteiro e desfaz os ALL anteriores.