Artigo com o objetivo de esclarecer com exemplos práticos para melhorar a compreensão sobre quais são as Diferenças entre UNION e UNION ALL
Para compreender de forma básica tem a ver com linhas duplicadas. UNION
combinará linhas das demais tabelas combinadas que já existam no resultado das consultas aplicadas nas tabelas anteriores; Já o UNION ALL
não se importará com isso.
Uma outra alternativa de como ver UNION
e UNION ALL
vem diretamente da matemática:
UNION
é uma operação de coleções de elementos que resulta em um terceiro conjunto;UNION ALL
é uma operação de coleções de elementos resultando em uma bag.
Aqui, tanto conjunto como bag são coleções de elementos. A diferença entre eles é dada pela “operação de soma” de elementos a uma coleção pré-existente. Vou definir como “soma”:
el + C = R
Onde el
é um elemento qualquer, C
é a coleção pré-existente e R
é a coleção resultante da operação, que contém em sua totalidade C
e também tem como elemento el
.
Se el
não existir previamente em C
,então as operações com conjunto e bagsão idênticas. Agora,se el
já existir previamente em C
,a soma com um conjunto resultará em R==C
,não afetando o resultante. Porém a bagé alterada com a adição de el
,portanto R !=C
.
De certo modo,podemos dizer que a bagé uma coleção de elementos que admite repetição,já conjunto não admite repetição.
Com isso,temos resultados interessantes ao se usar UNION
e UNION ALL
. Como conjunto não admite repetição,o UNION
comparará todas as tuplas e retornará apenas as únicas. Por uma questão de performance,a primeira operação a ser realizada será uma ordenação total das tuplas (tempo o(n log n)
) para então eliminar as repetições (tempo o(n)
). Se ele não fizesse essa ordenação antes da verificação dos únicos,teria um tempo de execução quadrático.
Normalmente se trabalha com números,strings e datas em um banco de dados,então nesse conjunto universo é possível obter uma ordenação. Pôde-se também usar uma heurística para ordenação de blobs,tratando-os como uma palavra de bytes e ordená-los lexicograficamente,mantendo portanto uma ordenação mais “natural”. Para o caso de enumerações,como elas possuem rótulos em strings,poderíamos usar esses rótulos e continuar com uma ordenação no conjunto (embora essa ordenação agora não seja mais uma ordenação natural).
Então,por questão de performance,ao requisitar um UNION
,normalmente o SGBD guardará todo o resultado da consulta,executará uma única ordenação no final de tudo e então obterá o resultado de tuplas únicas. Ele não faz ordenamentos parciais do conjunto de dados pois é extremamente ruim para a performance;executar uma ordenação a cada m
dados novos significa rodar o(n/m)
vezes uma ordenação de o(n log n)
,o que pode acabar ficando pior até mesmo que uma ordenação quadrática se m
for mal escolhido.
Isso tudo implica que UNION
não tem alta disponibilidade,pois só começará a retornar após obter todos os dados.
Já UNION ALL
,como tem como resultado uma bag,não precisa obter previamente todo o resultado para então devolver. No momento que um resultado é obtido,ele já pode responder imediatamente a quem fez a consulta,esquecer desse valor é pegar o próximo. Isso torna sua disponibilidademuito maior. Sem falar que potencialmente,dependendo de como foi implementado o motor de SQL usado,o resultado desse operador não precisa ser armazenado em memória,podendo ser retornado imediatamente para quem o chamou utilizar o dado obtido.
Por ventura,pode-se necessitar de se trabalhar com conjuntospropriamente ditos,não com bags,mas nem por isso você precisará usar UNION
. Claro,isso vai depender extremamente da semântica de cada caso,não recomendo portanto generalizar. Vou dar um exemplo em que é possível obter um conjunto a partir do uso de UNION ALL
.
Com base na modelagem a seguir:
cd_usuario
,que por sua vez se liga com cd_cliente
,ou cd_vendedor
,ou cd_supervisor
,dependendo da muliplexação. No meu caso,cada correntista só pode ter no máximo uma conta corrente. A consulta ficaria assim:
SELECTcd_usuario,"cliente" AStp_correntista,nm_cliente ASnm_correntista
FROMconta_corrente cc INNERJOINcliente c ON(c.cd_cliente =cc.cd_usuario)
WHEREcc.tp_conta ='c'UNIONSELECTcd_usuario,"vendedor" AStp_correntista,nm_vendedor ASnm_correntista
FROMconta_corrente cc INNERJOINvendedor v ON(v.cd_vendedor =cc.cd_usuario)
WHEREcc.tp_conta ='v'UNIONSELECTcd_usuario,"supervisor" AStp_correntista,nm_supervisor ASnm_correntista
FROMconta_corrente cc INNERJOINsupervisor s ON(s.cd_supervisor =cc.cd_usuario)
WHEREcc.tp_conta ='s'
Pronto,a consulta retorna um conjunto como esperado. Agora,notou como não é possível que,por acaso,haja uma igualdade de tuplas entre,digamos,a primeira consulta e a segunda consulta? Isso porque todos os elementos da primeira consulta terão como segundo elemento de suas tuplas o valor “cliente”,já os da segunda consulta o valor na mesma posição seria “vendedor”. Além disso,como cd_cliente
é chave primária da tabela cliente
e cada cliente
nesse modelo só está atrelado a no máximo um elemento da tabela conta_corrente
,então não tem choque de tuplas dentro de cada consulta individual,portanto cada uma das 3 consultas acima resulta num conjunto.
Como já temos 3 conjuntos,e temos garantia que nenhum desses conjuntos tem elemento em comum com outro conjunto,a operação de “soma”terá o mesmo resultado final que a “soma”de bags. Portanto,em casos assim,o uso do UNION ALL
garante o resultado desejado e também garante uma melhor performance (teoricamente pelo menos).
A consulta então pode ser reescrita assim:
SELECTcd_usuario,"cliente" AStp_correntista,nm_cliente ASnm_correntista
FROMconta_corrente cc INNERJOINcliente c ON(c.cd_cliente =cc.cd_usuario)
WHEREcc.tp_conta ='c'UNIONALLSELECTcd_usuario,"vendedor" AStp_correntista,nm_vendedor ASnm_correntista
FROMconta_corrente cc INNERJOINvendedor v ON(v.cd_vendedor =cc.cd_usuario)
WHEREcc.tp_conta ='v'UNIONALLSELECTcd_usuario,"supervisor" AStp_correntista,nm_supervisor ASnm_correntista
FROMconta_corrente cc INNERJOINsupervisor s ON(s.cd_supervisor =cc.cd_usuario)
WHEREcc.tp_conta ='s'
LEIA TAMBÉM: