EXISTE (SELECIONE 1…) vs EXISTA (SELECIONE *…) Um ou outro?

38

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 como:

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 é em NOT EXISTSvez de EXISTS: Em algumas ocasiões, eu posso escrevê-la com uma LEFT JOINe 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_keynão é tão óbvio, ou quando sua chave primária ou sua condição de junção é composta por várias colunas (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á.

  1. Existe alguma diferença (além do estilo) a ser usada em SELECT 1vez de SELECT *?
    Existe algum caso de esquina em que não se comporte da mesma maneira?

  2. Embora o que eu escrevi seja o padrão SQL (AFAIK): existe essa diferença para diferentes bancos de dados / versões mais antigas?

  3. Existe alguma vantagem em escrever explicitamente um antijoin?
    Os planejadores / otimizadores contemporâneos o tratam de maneira diferente da NOT EXISTScláusula?

joanolo
fonte
5
Observe que o PostgreSQL suporta seleções sem colunas, para que você possa escrever EXISTS (SELECT FROM ...).
rightfold
11
Eu tenho feito quase a mesma pergunta no SO há alguns anos atrás: stackoverflow.com/questions/7710153/…
Erwin Brandstetter:

Respostas:

45

Não, não há diferença na eficiência entre (NOT) EXISTS (SELECT 1 ...)e (NOT) EXISTS (SELECT * ...)em todos os principais DBMS. Eu já vi muitas vezes (NOT) EXISTS (SELECT NULL ...)sendo 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 prova que a expressão não é avaliada.


Sobre o LEFT JOIN / IS NULLmétodo antijoin, uma correção: isso é equivalente a NOT EXISTS (SELECT ...).

Nesse caso, NOT EXISTSvsLEFT JOIN / IS NULL, você pode obter diferentes planos de execução. No MySQL, por exemplo, e principalmente nas versões mais antigas (anteriores à 5.7), os planos seriam bastante semelhantes, mas não idênticos. Os otimizadores de outros DBMS (SQL Server, Oracle, Postgres, DB2) são - até onde eu sei - mais ou menos capazes de reescrever esses 2 métodos e considerar os mesmos planos para ambos. Ainda assim, não existe 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, configurações disponíveis etc.

Observe também que USINGnão pode ser usado em todos os DBMS (SQL Server, por exemplo). Os trabalhos mais comuns em JOIN ... ONtodos os lugares.
E as colunas precisam ser prefixadas com o nome da tabela / alias no SELECTpara evitar erros / ambiguidades quando tivermos junções.
Também geralmente prefiro colocar a coluna unida na IS NULLverificação (embora a PK ou qualquer coluna não anulável esteja OK, pode ser útil para a eficiência quando o plano LEFT JOINusa um índice não agrupado):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

Há também um terceiro método para antijogo, usando, NOT INmas ele tem semântica diferente (e resultados!) Se a coluna da tabela interna for anulável. No entanto, pode ser usado excluindo as linhas com NULL, tornando a consulta equivalente às 2 versões anteriores:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Isso também geralmente gera planos semelhantes na maioria dos DBMS.

ypercubeᵀᴹ
fonte
11
Até versões muito recentes do MySQL [NOT] IN (SELECT ...), embora equivalentes, tiveram um desempenho muito ruim. Evite isso!
Rick James
4
Isso não é verdade para o PostgreSQL . SELECT *certamente está fazendo mais trabalho. Eu faria para aconselhar simplicidade bem usandoSELECT 1
Evan Carroll
11

Há uma categoria de casos em que SELECT 1e SELECT *não são intercambiáveis ​​- mais especificamente, um sempre será aceito nesses casos, enquanto o outro na maioria das vezes não.

Estou falando de casos em que você precisa verificar a existência de linhas de um conjunto agrupado . Se a tabela Ttiver colunas C1e C2você estiver verificando a existência de grupos de linhas que correspondem a uma condição específica, você pode usar o SELECT 1seguinte:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

mas você não pode usar SELECT *da mesma maneira.

Esse é apenas um aspecto sintático. Onde as duas opções são aceitas sintaticamente, você provavelmente não terá nenhuma diferença em termos de desempenho ou resultados retornados, como foi explicado na outra resposta .

Notas adicionais após comentários

Parece que muitos produtos de banco de dados não suportam essa distinção. Produtos como SQL Server, Oracle, MySQL e SQLite aceitarão com prazer SELECT *a consulta acima sem erros, o que provavelmente significa que eles tratam EXISTS SELECTde uma maneira especial.

O 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:

ERRO: a coluna "T.C2" deve aparecer na cláusula GROUP BY ou ser usada em uma função agregada

Andriy M
fonte
11
Bons pontos, embora este não seja exatamente o caso que me preocupou. Este mostra uma diferença conceitual . Porque, quando você GROUP BY, o conceito de *não tem sentido (ou, pelo menos, não é tão claro).
Joanolo 29/12/16
5

Uma maneira discutivelmente interessante de reescrever a EXISTScláusula que resulta em uma consulta mais limpa e talvez menos enganosa, pelo menos no SQL Server, seria:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

A versão anti-semi-join seria semelhante a:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Ambos são normalmente otimizados para o mesmo plano que WHERE EXISTSou WHERE NOT EXISTS, mas a intenção é inconfundível e você não tem um "estranho" 1ou *.

Curiosamente, os problemas de verificação nula associados a NOT IN (...)são problemáticos <> ALL (...), enquanto o NOT EXISTS (...)não sofre com esse problema. Considere as duas tabelas a seguir com uma coluna anulável:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Adicionaremos alguns dados a ambos, com algumas linhas correspondentes e outras que não:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 2
| 3 3
| 4 NULL
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 2
| 3 NULL
| 4 4
+ -------- + ----------- +

A NOT IN (...)consulta:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Tem o seguinte plano:

insira a descrição da imagem aqui

A consulta não retorna linhas, pois os valores NULL tornam impossível a igualdade de confirmação.

Esta consulta, com <> ALL (...)mostra o mesmo plano e não retorna linhas:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

insira a descrição da imagem aqui

A variante utilizada NOT EXISTS (...)mostra uma forma de plano ligeiramente diferente e retorna linhas:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

O plano:

insira a descrição da imagem aqui

Os resultados dessa consulta:

+ -------- + ----------- +
| ID SomeValue |
+ -------- + ----------- +
| 3 3
| 4 NULL
+ -------- + ----------- +

Isso torna o uso <> ALL (...)tão propenso a resultados problemáticos quanto NOT IN (...).

Max Vernon
fonte
3
Devo dizer que não acho *estranho: leio EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... De qualquer forma, gosto de ter alternativas, e a sua é claramente legível. Uma dúvida / ressalva: como se comportará se bfor anulável? [Eu tive más experiências e algumas noites curtas ao tentar descobrir uma misstake causada por uma x IN (SELECT something_nullable FROM a_table)]
joanolo
EXISTS informa se uma tabela possui uma linha e retorna verdadeiro ou falso. EXISTS (SELECT x FROM (valores (nulo)) é verdadeiro. IN é = ANY e NOT IN é <> ALL. Estes 4 usam uma linha RHS com NULLs para corresponder. (X) = ANY (valores (nulo)) & (x) <> ALL (valores (nulo)) é desconhecido / nulo, mas EXISTS (valores (nulo)) é verdadeiro. (IN & = ANY tem o mesmo "problema de verificação nula associado a NOT IN (...) [& ] <> ALL (...) ". QUALQUER TUDO repete OR & AND. Mas existem apenas" problemas "se você não organizar a semântica como pretendida.) Não aconselha o uso para EXISTS. Eles são enganosos , não "menos enganoso".
philipxy 03/06
@phipriprxy - Se eu estiver errado, não tenho nenhum problema em admitir. Sinta-se à vontade para adicionar sua própria resposta, se quiser.
Max Vernon
4

A "prova" de que eles são idênticos (no MySQL) é fazer

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

depois repita com SELECT 1. Nos dois casos, a saída 'estendida' mostra que foi transformada em SELECT 1.

Da mesma forma, COUNT(*)é transformado em COUNT(0).

Outra coisa a ser observada: melhorias na otimização foram feitas nas versões recentes. Pode valer a pena comparar EXISTSvs anti-junções. Sua versão pode fazer um trabalho melhor com uma versus a outra.

Rick James
fonte
4

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 irá falhar.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

E isso terá sucesso.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Está falhando porque o seguinte falha, mas isso ainda significa que há uma diferença.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Você pode encontrar mais informações sobre essa peculiaridade em particular e a violação das especificações na minha resposta à pergunta: A Especificação SQL requer um GROUP BY em EXISTS ()

Evan Carroll
fonte
Um caso canto raro, um pouco estranho , talvez, mas mais uma vez, uma prova que você tem que fazer muitas concessões ao projetar um banco de dados ...
joanolo
-1

Eu sempre usei select top 1 'x'(SQL Server)

Teoricamente, select top 1 'x' seria mais eficiente que select *, como o primeiro estaria completo após selecionar uma constante na existência de uma linha de qualificação, 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.

G DeMasters
fonte
Faz sentido. Esse pode ser (ou poderia ter sido) um dos poucos casos em que top nsem order byé uma boa idéia.
joanolo
3
"Teoricamente, ...." Não, teoricamente select top 1 'x'não deve ser mais eficiente do que select *em uma Existexpressão. Na prática, pode ser mais eficiente se o otimizador estiver funcionando abaixo do ideal, mas teoricamente ambas as expressões são equivalentes.
precisa saber é o seguinte
-4

IF EXISTS(SELECT TOP(1) 1 FROMé um hábito melhor a longo prazo e entre plataformas, simplesmente porque você nem precisa começar a se preocupar com o quão boa ou ruim é a sua plataforma / versão atual; e SQL está passando de TOP npara parametrizável TOP(n). Essa deve ser uma habilidade de aprender uma vez.

ajeh
fonte
3
O que você quer dizer com "entre plataformas" ? TOPnem é SQL válido.
ypercubeᵀᴹ
"SQL está se movendo .." está completamente errado. Não existe TOP (n)no "SQL" - a linguagem de consulta padrão. Há um no T-SQL que é o dialeto que o Microsoft SQL Server está usando.
A_horse_with_no_name 28/08
A tag na pergunta original é "SQL Server". Mas não há problema em votar e contestar o que eu disse - é o objetivo deste site permitir uma votação fácil. Quem sou eu para chover no seu desfile com uma atenção chata aos detalhes?
ajeh