Sempre que preciso verificar a existência de alguma linha em uma tabela, costumo escrever sempre uma condição como:
SELECT a, b, c
FROM a_table
WHERE EXISTS
(SELECT * -- This is what I normally write
FROM another_table
WHERE another_table.b = a_table.b
)
Algumas outras pessoas escrevem assim:
SELECT a, b, c
FROM a_table
WHERE EXISTS
(SELECT 1 --- This nice '1' is what I have seen other people use
FROM another_table
WHERE another_table.b = a_table.b
)
Quando a condição é NOT EXISTS
em vez de EXISTS
: Em algumas ocasiões, posso escrevê-la com a LEFT JOIN
e uma condição extra (às vezes chamada de antijoin ):
SELECT a, b, c
FROM a_table
LEFT JOIN another_table ON another_table.b = a_table.b
WHERE another_table.primary_key IS NULL
Eu tento evitá-lo porque acho que o significado é menos claro, especialmente quando o que é seu primary_key
não é tão óbvio, ou quando sua chave primária ou sua condição de junção é multi-coluna (e você pode facilmente esquecer uma das colunas). No entanto, às vezes você mantém o código escrito por outra pessoa... e ele está lá.
Existe alguma diferença (além do estilo) para usar
SELECT 1
em vez deSELECT *
?
Existe algum caso de canto em que não se comporte da mesma maneira?Embora o que escrevi seja o SQL padrão (AFAIK): Existe essa diferença para diferentes bancos de dados / versões mais antigas?
Existe alguma vantagem em escrever explicitamente um antijoin?
Os planejadores/otimizadores contemporâneos a tratam de forma diferente daNOT EXISTS
cláusula?
Não, não há diferença de eficiência entre
(NOT) EXISTS (SELECT 1 ...)
e(NOT) EXISTS (SELECT * ...)
em todos os principais SGBDs. Muitas vezes eu vi(NOT) EXISTS (SELECT NULL ...)
ser usado também.Em alguns você pode até escrever
(NOT) EXISTS (SELECT 1/0 ...)
e o resultado é o mesmo - sem nenhum erro (divisão por zero), o que comprova que a expressão ali nem é avaliada.Sobre o
LEFT JOIN / IS NULL
método antijoin, uma correção: isso é equivalente aNOT EXISTS (SELECT ...)
.Neste caso,
NOT EXISTS
contraLEFT JOIN / IS NULL
, você pode obter diferentes planos de execução. No MySQL, por exemplo, e principalmente em versões mais antigas (antes de 5.7), os planos seriam bastante semelhantes, mas não idênticos. Os otimizadores de outros SGBDs (SQL Server, Oracle, Postgres, DB2) são - que eu saiba - mais ou menos capazes de reescrever esses 2 métodos e considerar os mesmos planos para ambos. Ainda assim, não há essa garantia e ao fazer a otimização, é bom verificar os planos de diferentes reescritas equivalentes, pois pode haver casos em que cada otimizador não reescreve (por exemplo, consultas complexas, com muitas junções e/ou tabelas derivadas / subconsultas dentro da subconsulta, onde as condições de várias tabelas, colunas compostas usadas nas condições de junção) ou as opções e planos do otimizador são afetados de maneira diferente pelos índices disponíveis, configurações etc.Observe também que
USING
não pode ser usado em todos os DBMS (SQL Server por exemplo). O mais comumJOIN ... ON
funciona em todos os lugares.E as colunas precisam ser prefixadas com o nome/alias da tabela
SELECT
para evitar erros/ambiguidades quando temos junções.Eu também geralmente prefiro colocar a coluna unida na
IS NULL
verificação (embora o PK ou qualquer coluna não anulável esteja OK, pode ser útil para eficiência quando o planoLEFT JOIN
usa um índice não clusterizado):Há também um terceiro método para antijoins, usando
NOT IN
mas isso tem semântica diferente (e resultados!) se a coluna da tabela interna for anulável. Ele pode ser usado excluindo as linhas comNULL
, tornando a consulta equivalente às 2 versões anteriores:Isso também geralmente produz planos semelhantes na maioria dos SGBDs.
Há uma categoria de casos em que
SELECT 1
eSELECT *
não são intercambiáveis – mais especificamente, um sempre será aceito nesses casos, enquanto o outro não será.Estou falando de casos em que você precisa verificar a existência de linhas de um conjunto agrupado . Se a tabela
T
tiver colunasC1
eC2
você estiver verificando a existência de grupos de linhas que correspondem a uma condição específica, você pode usarSELECT 1
assim:mas você não pode usar
SELECT *
da mesma maneira.Isso é apenas um aspecto sintático. Onde ambas as opções são aceitas sintaticamente, você provavelmente não terá diferença em termos de desempenho ou resultados retornados, conforme explicado na outra resposta .
Notas adicionais após comentários
Parece que poucos produtos de banco de dados realmente suportam essa distinção. Produtos como SQL Server, Oracle, MySQL e SQLite aceitarão alegremente
SELECT *
a consulta acima sem nenhum erro, o que provavelmente significa que eles tratam um EXISTSSELECT
de uma maneira especial.PostgreSQL é um RDBMS onde
SELECT *
pode falhar, mas ainda pode funcionar em alguns casos. Em particular, se você estiver agrupando pelo PK,SELECT *
funcionará bem, caso contrário, falhará com a mensagem:A "prova" de que eles são idênticos (no MySQL) é fazer
depois repita com
SELECT 1
. Em ambos os casos, a saída 'estendida' mostra que foi transformada emSELECT 1
.Da mesma forma,
COUNT(*)
é transformado emCOUNT(0)
.Outra coisa a ser observada: melhorias de otimização foram feitas em versões recentes. Pode valer a pena comparar
EXISTS
com anti-junções. Sua versão pode fazer um trabalho melhor com um do que com o outro.Uma maneira indiscutivelmente interessante de reescrever a
EXISTS
cláusula que resulta em uma consulta mais limpa e talvez menos enganosa, pelo menos no SQL Server, seria:A versão anti-semi-join seria assim:
Ambos são normalmente otimizados para o mesmo plano que
WHERE EXISTS
ouWHERE NOT EXISTS
, mas a intenção é inconfundível, e você não tem "estranho"1
ou*
.Curiosamente, os problemas de verificação de nulos associados
NOT IN (...)
são problemáticos para<> ALL (...)
, enquanto oNOT EXISTS (...)
não sofre desse problema. Considere as duas tabelas a seguir com uma coluna anulável:Adicionaremos alguns dados a ambos, com algumas linhas que correspondem e outras que não:
A
NOT IN (...)
consulta:Tem o seguinte plano:
A consulta não retorna nenhuma linha, pois os valores NULL tornam a igualdade impossível de confirmar.
Esta consulta, com
<> ALL (...)
mostra o mesmo plano e não retorna nenhuma linha:A variante usando
NOT EXISTS (...)
, mostra uma forma de plano ligeiramente diferente e retorna linhas:O plano:
Os resultados dessa consulta:
Isso torna o uso
<> ALL (...)
tão propenso a resultados problemáticos quanto oNOT IN (...)
.Em alguns bancos de dados essa otimização ainda não funciona. Como por exemplo no PostgreSQL A partir da versão 9.6, isso falhará.
E isso vai dar certo.
Está falhando porque o seguinte falha, mas isso ainda significa que há uma diferença.
Você pode encontrar mais informações sobre essa peculiaridade e violação da especificação em minha resposta à pergunta, A especificação do SQL requer um GROUP BY em EXISTS ()
Sempre usei
select top 1 'x'
(SQL Server)Teoricamente,
select top 1 'x'
seria mais eficiente queselect *
, pois o primeiro estaria completo após selecionar uma constante sobre a existência de uma linha qualificadora, enquanto o segundo selecionaria tudo.NO ENTANTO, embora muito cedo possa ter sido relevante, a otimização tornou a diferença irrelevante em provavelmente todos os principais RDBS.
IF EXISTS(SELECT TOP(1) 1 FROM
é um hábito melhor a longo prazo e entre plataformas simplesmente porque você não precisa nem começar a se preocupar com o quão boa ou ruim é sua plataforma/versão atual; e SQL está se movendoTOP n
para parametrizávelTOP(n)
. Esta deve ser uma habilidade de aprender uma vez.