Siempre prefiero NOT EXISTS
.
Los planes de ejecución pueden ser los mismos en este momento, pero si cualquiera de las columnas se modifica en el futuro para permitir NULL
es el NOT IN
versión tendrá que hacer más trabajo (incluso si no hay NULL
s están realmente presentes en los datos) y la semántica de NOT IN
si NULL
s son presente es poco probable que sean los que desea de todos modos.
Cuando ninguno de los Products.ProductID
o [Order Details].ProductID
permitir NULL
es el NOT IN
será tratada de forma idéntica a la siguiente consulta.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
El plan exacto puede variar, pero para mis datos de ejemplo obtengo lo siguiente.
Un error razonablemente común parece ser que las subconsultas correlacionadas siempre son "malas" en comparación con las uniones. Ciertamente pueden serlo cuando fuerzan un plan de bucles anidados (subconsulta evaluada fila por fila) pero este plan incluye un operador lógico anti semi-unión. Las uniones antisemi no están restringidas a bucles anidados, pero también pueden usar uniones hash o fusionadas (como en este ejemplo).
/*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
Si [Order Details].ProductID
es NULL
-able la consulta se convierte en
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)
La razón de esto es que la semántica correcta es [Order Details]
contiene cualquier NULL
ProductId
s es para no devolver resultados. Consulte el carrete de recuento de filas y antisemi unión adicional para verificar que se agregue al plan.
Si Products.ProductID
también se cambia para convertirse en NULL
-able la consulta se convierte en
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)
La razón de eso es porque un NULL
Products.ProductId
no debe devolverse en los resultados excepto si el NOT IN
la subconsulta no arrojara ningún resultado (es decir, el [Order Details]
la mesa está vacía). En cuyo caso debería. En el plan para mis datos de muestra, esto se implementa agregando otra combinación anti semi como se muestra a continuación.
El efecto de esto se muestra en la publicación del blog ya vinculada por Buckley. En el ejemplo, el número de lecturas lógicas aumenta de alrededor de 400 a 500 000.
Además, el hecho de que un solo NULL
puede reducir el recuento de filas a cero hace que la estimación de la cardinalidad sea muy difícil. Si SQL Server asume que esto sucederá pero de hecho no hubo NULL
filas en los datos, el resto del plan de ejecución puede ser catastróficamente peor, si esto es solo parte de una consulta más grande, con bucles anidados inapropiados que provocan la ejecución repetida de un subárbol costoso, por ejemplo.
Este no es el único plan de ejecución posible para un NOT IN
en un NULL
columna capaz sin embargo. Este artículo muestra otro para una consulta contra AdventureWorks2008
base de datos.
Para el NOT IN
en un NOT NULL
columna o NOT EXISTS
contra una columna anulable o no anulable da el siguiente plan.
Cuando la columna cambia a NULL
-habilitar el NOT IN
el plan ahora parece
Agrega un operador de unión interna adicional al plan. Este aparato se explica aquí. Todo está ahí para convertir la búsqueda de índice correlacionado único anterior en Sales.SalesOrderDetail.ProductID = <correlated_product_id>
a dos búsquedas por fila exterior. El adicional está en WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Como esto está bajo una combinación anti semi, si esa devuelve alguna fila, la segunda búsqueda no ocurrirá. Sin embargo, si Sales.SalesOrderDetail
no contiene ningún NULL
ProductID
s duplicará el número de operaciones de búsqueda requeridas.