sql >> Base de Datos >  >> RDS >> Sqlserver

NO EN vs NO EXISTE

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.