Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

NÃO ESTÁ vs NÃO EXISTE


Eu sempre uso como padrão NOT EXISTS .

Os planos de execução podem ser os mesmos no momento, mas se qualquer coluna for alterada no futuro para permitir NULL é o NOT IN terá de trabalhar mais (mesmo se não houver NULL s estão realmente presentes nos dados) e a semântica de NOT IN se NULL s são presentes são improváveis ​​de serem os que você quer de qualquer maneira.

Quando nem Products.ProductID ou [Order Details].ProductID permitir NULL é o NOT IN será tratado de forma idêntica à consulta a seguir.
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 meus dados de exemplo, recebo o seguinte.



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-semi-junção. Anti semijunções não sã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 

Se [Order Details].ProductID é NULL -able a consulta então se torna
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 qualquer NULL ProductId s é não retornar nenhum resultado. Consulte o spool de contagem de linhas e anti semijunção extra para verificar se isso é adicionado ao plano.



Se Products.ProductID também é alterado para se tornar NULL -able a consulta então se torna
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 é porque um NULL Products.ProductId não deve ser retornado nos resultados exceto se o NOT IN subconsulta não retornasse nenhum resultado (ou seja, o [Order Details] tabela está vazia). Nesse caso deveria. No plano para meus dados de exemplo, isso é implementado adicionando outra anti semijunção, conforme abaixo.



O efeito disso é mostrado na 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 NULL pode reduzir a contagem de linhas para zero torna a estimativa de cardinalidade muito difícil. Se o SQL Server assumir que isso acontecerá, mas na verdade não houve NULL linhas nos dados, o resto do plano de execução pode ser catastroficamente pior, se isso for apenas parte de uma consulta maior, com loops aninhados inadequados causando a execução repetida de uma subárvore cara, por exemplo.

Este não é o único plano de execução possível para um NOT IN em um NULL -able coluna no entanto. Este artigo mostra outro para uma consulta no AdventureWorks2008 base de dados.

Para o NOT IN em um NOT NULL coluna ou NOT EXISTS em relação a uma coluna anulável ou não anulável, ele fornece o plano a seguir.



Quando a coluna muda para NULL -ativa o NOT IN plano agora parece



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

Como isso está sob uma anti semi-junção, se essa retornar qualquer linha, a segunda busca não ocorrerá. No entanto, se Sales.SalesOrderDetail não contém nenhum NULL ProductID s dobrará o número de operações de busca necessárias.