NOT IN vs NOT EXISTS

538

Qual dessas consultas é a mais rápida?

NÃO EXISTE:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

Ou NÃO EM:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

O plano de execução da consulta diz que ambos fazem a mesma coisa. Se for esse o caso, qual é o formulário recomendado?

Isso é baseado no banco de dados NorthWind.

[Editar]

Acabei de encontrar este artigo útil: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Eu acho que vou ficar com NÃO EXISTE.

ilitirit
fonte
3
você tentou o plano usando uma junção esquerda, onde é nulo?
Sebas
1
NOT IN e NOT EXISTS não são idênticos. Ter um olhar para este link para diferença entre eles: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale
2
Gostaria de saber se os bancos de dados diferem, mas no meu último benchmark contra o PostgreSQL, isso NOT IN consulta: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)é quase 30 vezes mais rápida que esta NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn 4/12/12
1
@rcdmk Você verificou a data das perguntas?
ilitirit

Respostas:

693

Eu sempre padrão para NOT EXISTS.

Os planos de execução podem ser os mesmos no momento, mas se alguma coluna for alterada no futuro para permitir NULL s, a NOT INversão precisará fazer mais trabalho (mesmo se nenhum NULLs estiver realmente presente nos dados) e a semântica de NOT INse NULLs estiver presente provavelmente não será o que você deseja.

Quando nenhum Products.ProductIDou [Order Details].ProductIDpermitir NULLs NOT INserão tratados de forma idêntica à seguinte consulta.

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

O plano exato pode variar, mas para os meus dados de exemplo, recebo o seguinte.

Nem NULL

Um equívoco razoavelmente comum parece ser que as subconsultas correlacionadas são sempre "ruins" em comparação com as junções. Eles certamente podem ser quando forçam um plano de loops aninhados (subconsulta avaliada linha por linha), mas esse plano inclui um operador lógico anti-junção. As junções anti-semi não estão restritas a loops aninhados, mas também podem usar junções de hash ou mesclagem (como neste exemplo).

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

E se [Order Details].ProductID estiver NULLativável, a consulta se tornará

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

A razão para isso é que a semântica correta, se [Order Details]contém algumaNULL ProductId s, não retornará resultados. Consulte o spool anti-junção extra e contagem de linhas para verificar se foi adicionado ao plano.

Um NULL

Se Products.ProductIDtambém for alterado para se tornarNULL possível, a consulta se tornará

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

A razão para isso é NULL Products.ProductIdque a não deve ser retornada nos resultados, exceto se a NOT INsubconsulta não retornar nenhum resultado (isto é, o[Order Details] tabela está vazia). Nesse caso, deveria. No plano para meus dados de amostra, isso é implementado adicionando outra anti-junção como abaixo.

Ambos NULL

O efeito disso é mostrado em postagem do blog já vinculada por Buckley . No exemplo, o número de leituras lógicas aumenta de cerca de 400 para 500.000.

Além disso, o fato de um único NULLpoder reduzir a contagem de linhas para zero dificulta muito a estimativa da cardinalidade. Se o SQL Server assumir que isso acontecerá, mas, de fato, não houveNULL linhas nos dados, o restante do plano de execução poderá ser catastroficamente pior, se isso for apenas parte de uma consulta maior, com loops aninhados inadequados, causando a execução repetida de um sub caro árvore por exemplo .

Entretanto, este não é o único plano de execução possível para uma coluna NOT INon NULL-able.Este artigo mostra outro para uma consulta no AdventureWorks2008banco de dados.

Para a NOT INsobre uma NOT NULLcoluna ou oNOT EXISTS contra anulável ou não anulável, fornece o plano a seguir.

Não existe

Quando a coluna muda para NULL-able, oNOT IN plano agora parece

Not In - Null

Ele adiciona um operador de junção interna extra ao plano. Este aparelho é explicado aqui . Está tudo lá para converter a busca de índice correlativo único anterior emSales.SalesOrderDetail.ProductID = <correlated_product_id> duas buscas por linha externa. O outro está ativado WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Como isso está sob uma junção anti-semi, se essa retornar linhas, a segunda busca não ocorrerá. No entanto, se Sales.SalesOrderDetailnão contiver nenhum NULL ProductIDs, dobrará o número de operações de busca necessárias.

Martin Smith
fonte
4
Posso perguntar como você obtém o gráfico de criação de perfis como mostrado?
XIS
5
@xis Estes são os planos de execução abertos no SQL Sentry plan explorer. Você também pode visualizar os planos de execução graficamente no SSMS.
Martin Smith
Eu aprecio isso pela única razão que: NOT EXISTSfunciona da maneira que eu espero NOT INque funcione (o que não funciona).
precisa saber é o seguinte
Com NOT EXISTS, tento usar SELECT 1, como NOT EXISTS (SELECT 1 FROM sometable WHERE alguma coisa), para que o banco de dados não precise retornar colunas do disco. Usar EXPLAIN para determinar se isso faz diferença no seu caso é provavelmente uma boa ideia.
Mayur Patel
4
@ Mayur Não há necessidade disso no SQL Server. stackoverflow.com/questions/1597442/…
Martin Smith
84

Lembre-se também de que NOT IN não é equivalente a NOT EXISTS quando se trata de nulo.

Este post explica muito bem

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

Quando a subconsulta retorna um nulo, NOT IN não corresponderá a nenhuma linha.

A razão para isso pode ser encontrada olhando os detalhes do que realmente significa a operação NOT IN.

Digamos, para fins de ilustração, que existem 4 linhas na tabela chamada t, há uma coluna chamada ID com valores 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

é equivalente a

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

Vamos dizer ainda que AVal é NULL onde ID = 4. Portanto, essa comparação! = Retorna DESCONHECIDA. A tabela de verdade lógica para AND indica que DESCONHECIDO e VERDADEIRO é DESCONHECIDO, DESCONHECIDO e FALSO é FALSO. Não há valor que possa ser AND com UNKNOWN para produzir o resultado TRUE

Portanto, se qualquer linha dessa subconsulta retornar NULL, todo o operador NOT IN será avaliado como FALSE ou NULL e nenhum registro será retornado.

Buckley
fonte
24

Se o planejador de execução diz que eles são iguais, eles são iguais. Use o que quer que torne sua intenção mais óbvia - neste caso, o segundo.

John Millikin
fonte
3
o tempo do planejador de execução pode ser o mesmo, mas os resultados da execução podem diferir, portanto há uma diferença. NOT IN produzirá resultados inesperados se você tiver NULL no seu conjunto de dados (consulte a resposta de buckley). Melhor usar NÃO EXISTE como padrão.
13133 nanonerd
15

Na verdade, acredito que isso seria o mais rápido:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
James Curran
fonte
2
Pode não ser o mais rápido quando o otimizador está executando seu trabalho, mas certamente será mais rápido quando não estiver.
Cade Roux
2
Ele pode ter simplificado sua consulta para este post também
Kip
1
Concordar A junção externa esquerda geralmente é mais rápida que uma subconsulta.
HLGEM
7
@HLGEM Discordo. Na minha experiência, o melhor caso para o LOJ é que eles são iguais e o SQL Server converte o LOJ em uma junção anti-semi. Na pior das hipóteses, o SQL Server LEFT JOINs tudo e filtra os NULLs, após o que pode ser muito mais ineficiente. Exemplo disso na parte inferior deste artigo
Martin Smith
12

Eu tenho uma tabela que tem cerca de 120.000 registros e preciso selecionar apenas aqueles que não existem (correspondidos a uma coluna varchar) em outras quatro tabelas com número de linhas aproximadamente 1500, 4000, 40000, 200. Todas as tabelas envolvidas têm índice exclusivo na Varcharcoluna em questão .

NOT IN levou cerca de 10 minutos, NOT EXISTS levou 4 segundos.

Eu tenho uma consulta recursiva que pode ter alguma seção sem sintonia que pode ter contribuído para os 10 minutos, mas a outra opção leva 4 segundos, pelo menos para mim que NOT EXISTSé muito melhor ou pelo menos isso INe EXISTSnão é exatamente o mesmo e sempre vale a pena. verifique antes de prosseguir com o código.

Yella Chalamala
fonte
8

No seu exemplo específico, eles são iguais, porque o otimizador descobriu o que você está tentando fazer é o mesmo nos dois exemplos. Mas é possível que em exemplos não triviais o otimizador possa não fazer isso e, nesse caso, há razões para preferir um ao outro ocasionalmente.

NOT INdeve ser preferido se você estiver testando várias linhas em sua seleção externa. A subconsulta dentro da NOT INinstrução pode ser avaliada no início da execução e a tabela temporária pode ser verificada em relação a cada valor na seleção externa, em vez de executar novamente a subseleção sempre que necessário com o parâmetroNOT EXISTS instrução.

Se a subconsulta precisar ser correlacionada com a seleção externa, NOT EXISTSpoderá ser preferível, pois o otimizador poderá descobrir uma simplificação que impede a criação de tabelas temporárias para executar a mesma função.

Jeffrey L Whitledge
fonte
6

Eu estava usando

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

e descobriu que estava dando resultados errados (por errado, não quero dizer resultados). Como havia um NULL em TABLE2.Col1.

Ao alterar a consulta para

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

me deu os resultados corretos.

Desde então, comecei a usar NOT EXISTS em todos os lugares.

ravish.hacker
fonte
5

Eles são muito parecidos, mas não são realmente iguais.

Em termos de eficiência, descobri que a instrução de associação à esquerda é nula mais eficiente (quando é necessário selecionar uma abundância de linhas)

Onga Leo-Yoda Vellem
fonte
2

Se o otimizador disser que são os mesmos, considere o fator humano. Eu prefiro ver NÃO EXISTE :)

um dia quando
fonte
1

Essa é uma pergunta muito boa, então decidi escrever um artigo muito detalhado sobre esse tópico no meu blog.

Modelo de tabela de banco de dados

Vamos supor que temos as duas tabelas a seguir em nosso banco de dados, que formam um relacionamento de tabela um para muitos.

Tabelas SQL EXISTS

A studenttabela é o pai e a student_gradetabela filha, pois possui uma coluna Chave estrangeira student_id que faz referência à coluna Chave primária id na tabela do aluno.

O student tablecontém os dois registros a seguir:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

E, a student_gradetabela armazena as notas que os alunos receberam:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTS

Digamos que queremos que todos os alunos que tenham recebido nota 10 na aula de matemática.

Se estivermos interessados ​​apenas no identificador de aluno, podemos executar uma consulta como esta:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Mas, o aplicativo está interessado em exibir o nome completo de a student, não apenas o identificador, por isso precisamos de informações da studenttabela também.

Para filtrar os studentregistros que possuem nota 10 em matemática, podemos usar o operador EXISTS SQL, assim:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Ao executar a consulta acima, podemos ver que apenas a linha Alice está selecionada:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

A consulta externa seleciona as studentcolunas da linha que estamos interessados ​​em retornar ao cliente. No entanto, a cláusula WHERE está usando o operador EXISTS com uma subconsulta interna associada.

O operador EXISTS retornará true se a subconsulta retornar pelo menos um registro e false se nenhuma linha for selecionada. O mecanismo do banco de dados não precisa executar a subconsulta inteiramente. Se um único registro for correspondido, o operador EXISTS retornará true e a outra linha de consulta associada será selecionada.

A subconsulta interna é correlacionada porque a coluna student_id da student_gradetabela é comparada com a coluna id da tabela externa do aluno.

SQL NÃO EXISTE

Vamos considerar que queremos selecionar todos os alunos que não têm nota inferior a 9. Para isso, podemos usar NOT EXISTS, o que nega a lógica do operador EXISTS.

Portanto, o operador NOT EXISTS retornará true se a subconsulta subjacente não retornar nenhum registro. No entanto, se um único registro for correspondido pela subconsulta interna, o operador NOT EXISTS retornará false e a execução da subconsulta poderá ser interrompida.

Para corresponder a todos os registros de alunos que não tenham associado student_grade a um valor menor que 9, podemos executar a seguinte consulta SQL:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

Ao executar a consulta acima, podemos ver que apenas o registro de Alice é correspondido:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Portanto, a vantagem de usar os operadores SQL EXISTS e NOT EXISTS é que a execução da subconsulta interna pode ser interrompida enquanto for encontrado um registro correspondente.

Vlad Mihalcea
fonte
-1

Depende..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

não seria relativamente lento, não há muito para limitar o tamanho do que a consulta verifica para ver se a chave está inserida. EXISTS seria preferível nesse caso.

Mas, dependendo do otimizador do DBMS, isso não poderia ser diferente.

Como um exemplo de quando EXISTS é melhor

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria
Greg Ogle
fonte
1
INe EXISTS obtenha o mesmo plano no SQL Server . A questão é sobre NOT INvs de NOT EXISTSqualquer maneira.
Martin Smith