NO EN CONTRA NO EXISTE

538

¿Cuál de estas consultas es la más rápida?

NO EXISTE:

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

O NO EN:

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

El plan de ejecución de la consulta dice que ambos hacen lo mismo. Si ese es el caso, ¿cuál es la forma recomendada?

Esto se basa en la base de datos NorthWind.

[Editar]

Acabo de encontrar este útil artículo: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Creo que me quedaré con NO EXISTE.

ilitirit
fuente
3
¿probaste el plan usando una combinación izquierda donde es nulo?
Sebas
1
NOT IN y NOT EXISTS no son idénticos. Eche un vistazo a este enlace para ver la diferencia entre ellos: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Gokhale
2
Me pregunto si las bases de datos difieren, pero en mi último punto de referencia contra PostgreSQL, esta NOT INconsulta: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)es casi 30 veces más 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
1
@rcdmk ¿Verificó la fecha de las preguntas?
ilitirit

Respuestas:

693

Siempre por defecto NOT EXISTS.

Los planes de ejecución pueden ser los mismos en este momento, pero si alguna de las columnas se modifica en el futuro para permitir que NULLla NOT INversión tenga que hacer más trabajo (incluso si no hay NULLs realmente presentes en los datos) y la semántica de NOT INif NULLs está presente es poco probable que sean los que quieres de todos modos.

Cuando ninguno Products.ProductIDo [Order Details].ProductIDpermitir que NULLs el NOT INserán tratados 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.

Ni NULL

Una idea errónea razonablemente común parece ser que las subconsultas correlacionadas siempre son "malas" en comparación con las uniones. Ciertamente pueden ser cuando fuerzan un plan de bucles anidados (subconsulta evaluada fila por fila) pero este plan incluye un operador lógico anti semiunión. Las semiuniones anti no están restringidas a bucles anidados, sino que también pueden usar combinaciones hash o merge (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].ProductIDes NULLposible, 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 si [Order Details]contiene alguna NULL ProductIds es no devolver resultados. Vea el carrete extra anti semi join y row count para verificar esto que se agrega al plan.

Un NULL

Si Products.ProductIDtambién se cambia para que se NULLpueda habilitar, 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 esto es porque NULL Products.ProductIdno se debe devolver a en los resultados, excepto si la NOT INsubconsulta no devuelve ningún resultado (es decir, la [Order Details]tabla está vacía). En cuyo caso debería. En el plan para mis datos de muestra, esto se implementa mediante la adición de otra semi-unión como se muestra a continuación.

Ambos NULL

El efecto de esto se muestra en la publicación del blog ya vinculada por Buckley . En el ejemplo allí, el número de lecturas lógicas aumenta de alrededor de 400 a 500,000.

Además, el hecho de que un solo NULLpuede 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 había NULLfilas 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 causan la ejecución repetida de un sub costoso árbol por ejemplo .

Sin embargo, este no es el único plan de ejecución posible para una columna NOT INen una NULLtabla. Este artículo muestra otro para una consulta en la AdventureWorks2008base de datos.

Para el NOT INen una NOT NULLcolumna o en NOT EXISTScontra de una columna anulable o no anulable da el siguiente plan.

No existe

Cuando la columna cambia a NULL-able, el NOT INplan ahora se ve como

No en - Nulo

Agrega un operador de unión interno adicional al plan. Este aparato se explica aquí . Está todo allí para convertir la búsqueda de índice correlacionada anterior simple en Sales.SalesOrderDetail.ProductID = <correlated_product_id>dos búsquedas por fila exterior. El adicional está encendido WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Como esto está bajo una semi-unión si esa devuelve alguna fila, la segunda búsqueda no ocurrirá. Sin embargo, si Sales.SalesOrderDetailno contiene ninguno NULL ProductID, duplicará el número de operaciones de búsqueda requeridas.

Martin Smith
fuente
44
¿Puedo preguntar cómo se obtiene el gráfico de perfil como se muestra?
xis
55
@xis Estos son planes de ejecución abiertos en el explorador de planes SQL Sentry. También puede ver los planes de ejecución gráficamente en SSMS.
Martin Smith
Aprecio esto por la única razón de que: NOT EXISTSfunciona de la manera que espero NOT INque funcione (lo cual, no es así).
levininja
Con NOT EXISTS, trato de usar SELECT 1 como NOT EXISTS (SELECT 1 FROM sometable WHERE something) para que la base de datos no necesite devolver columnas del disco. Usar EXPLAIN para determinar si esto hace una diferencia en su caso es probablemente una buena idea.
Mayur Patel
44
@ May No hay necesidad de esto en SQL Server. stackoverflow.com/questions/1597442/…
Martin Smith
84

También tenga en cuenta que NOT IN no es equivalente a NOT EXISTS cuando se trata de nulo.

Este post lo explica muy bien

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

Cuando la subconsulta devuelve incluso un valor nulo, NOT IN no coincidirá con ninguna fila.

La razón de esto se puede encontrar mirando los detalles de lo que realmente significa la operación NOT IN.

Digamos, para fines ilustrativos, que hay 4 filas en la tabla llamadas t, hay una columna llamada ID con valores 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

es 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)

Digamos además que AVal es NULL donde ID = 4. ¡De ahí que! = La comparación devuelve DESCONOCIDO. La tabla de verdad lógica para AND establece que DESCONOCIDO y VERDADERO es DESCONOCIDO, DESCONOCIDO y FALSO es FALSO. No hay ningún valor que se pueda AND AND con UNKNOWN para producir el resultado VERDADERO

Por lo tanto, si cualquier fila de esa subconsulta devuelve NULL, todo el operador NOT IN evaluará FALSE o NULL y no se devolverán registros

Buckley
fuente
24

Si el planificador de ejecución dice que son lo mismo, son lo mismo. Use el que haga que su intención sea más obvia, en este caso, la segunda.

John Millikin
fuente
3
El tiempo del planificador de ejecución puede ser el mismo, pero los resultados de la ejecución pueden diferir, por lo que hay una diferencia. NOT IN producirá resultados inesperados si tiene NULL en su conjunto de datos (vea la respuesta de Buckley). Es mejor usar NO EXISTE por defecto.
nanonerd
15

En realidad, creo que este sería el más 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
fuente
2
Puede que no sea el más rápido cuando el optimizador está haciendo su trabajo, pero ciertamente será más rápido cuando no lo esté.
Cade Roux
2
Es posible que también haya simplificado su consulta para esta publicación
Kip
1
De acuerdo La combinación externa izquierda suele ser más rápida que una subconsulta.
HLGEM
77
@HLGEM No estoy de acuerdo. En mi experiencia, el mejor caso para LOJ es que son iguales y SQL Server convierte el LOJ en un anti semi-join. En el peor de los casos, SQL Server IZQUIERDA UNE todo y filtra los NULL después de lo cual puede ser mucho más ineficiente. Ejemplo de eso al final de este artículo
Martin Smith
12

Tengo una tabla que tiene aproximadamente 120,000 registros y necesito seleccionar solo aquellos que no existen (emparejados con una columna varchar) en otras cuatro tablas con un número de filas de aproximadamente 1500, 4000, 40000, 200. Todas las tablas involucradas tienen un índice único en la Varcharcolumna correspondiente .

NOT INtomó unos 10 minutos, NOT EXISTStomó 4 segundos.

Tengo una consulta recursiva que podría tener una sección no ajustada que podría haber contribuido a los 10 minutos, pero la otra opción que toma 4 segundos explica, al menos para mí eso NOT EXISTSes mucho mejor o al menos eso INy EXISTSno son exactamente lo mismo y siempre valen la pena. verifique antes de continuar con el código.

Yella Chalamala
fuente
8

En su ejemplo específico, son iguales, porque el optimizador ha descubierto que lo que está tratando de hacer es lo mismo en ambos ejemplos. Pero es posible que en ejemplos no triviales el optimizador no haga esto, y en ese caso hay razones para preferir uno a otro en ocasiones.

NOT INdebe preferirse si está probando varias filas en su selección externa. La subconsulta dentro de la NOT INdeclaración se puede evaluar al comienzo de la ejecución, y la tabla temporal se puede verificar contra cada valor en la selección externa, en lugar de volver a ejecutar la subselección cada vez que se requeriría con la NOT EXISTSdeclaración.

Si la subconsulta debe correlacionarse con la selección externa, entonces NOT EXISTSpuede ser preferible, ya que el optimizador puede descubrir una simplificación que impide la creación de tablas temporales para realizar la misma función.

Jeffrey L Whitledge
fuente
6

Yo estaba usando

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

y descubrí que estaba dando resultados incorrectos (por incorrecto quiero decir que no hay resultados). Como había un NULL en TABLE2.Col1.

Al cambiar la consulta a

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

me dio los resultados correctos.

Desde entonces, comencé a usar NO EXISTE en todas partes.

ravish.hacker
fuente
5

Son muy similares pero no son realmente lo mismo.

En términos de eficiencia, descubrí que la combinación izquierda es más nula (cuando se selecciona una gran cantidad de filas)

Onga Leo-Yoda Vellem
fuente
2

Si el optimizador dice que son iguales, considere el factor humano. Prefiero ver NO EXISTE :)

un día cuando
fuente
1

Esta es una muy buena pregunta, así que decidí escribir un artículo muy detallado sobre este tema en mi blog.

Modelo de tabla de base de datos

Supongamos que tenemos las siguientes dos tablas en nuestra base de datos, que forman una relación de tabla de uno a muchos.

Tablas de SQL EXISTS

La studenttabla es el padre, y elstudent_grade tabla secundaria, ya que tiene una columna de clave externa student_id que hace referencia a la columna de clave principal id en la tabla del alumno.

El student tablecontiene los siguientes dos registros:

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

Y, la student_gradetabla almacena las calificaciones que recibieron los estudiantes:

| 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 EXISTE

Digamos que queremos que todos los estudiantes que hayan recibido un grado 10 en la clase de matemáticas.

Si solo estamos interesados ​​en el identificador de estudiante, entonces podemos ejecutar una 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

Pero, la aplicación está interesada en mostrar el nombre completo de a student, no solo el identificador, por lo que también necesitamos información de la studenttabla.

Para filtrar los studentregistros que tienen una calificación de 10 en matemáticas, podemos usar el operador EXISTS SQL, así:

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

Al ejecutar la consulta anterior, podemos ver que solo se selecciona la fila Alice:

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

La consulta externa selecciona las studentcolumnas de fila que estamos interesados ​​en devolver al cliente. Sin embargo, la cláusula WHERE está utilizando el operador EXISTS con una subconsulta interna asociada.

El operador EXISTS devuelve verdadero si la subconsulta devuelve al menos un registro y falso si no se selecciona ninguna fila. El motor de base de datos no tiene que ejecutar la subconsulta por completo. Si se coincide con un solo registro, el operador EXISTS devuelve verdadero y se selecciona la otra fila de consulta asociada.

La subconsulta interna está correlacionada porque la columna student_id de la student_gradetabla se compara con la columna id de la tabla externa del estudiante.

SQL NO EXISTE

Consideremos que queremos seleccionar a todos los estudiantes que no tengan una calificación inferior a 9. Para esto, podemos usar NOT EXISTS, que niega la lógica del operador EXISTS.

Por lo tanto, el operador NO EXISTE devuelve verdadero si la subconsulta subyacente no devuelve ningún registro. Sin embargo, si la subconsulta interna coincide con un solo registro, el operador NO EXISTE devolverá falso y la ejecución de la subconsulta puede detenerse.

Para hacer coincidir todos los registros de estudiantes que no tienen un student_grade asociado con un valor inferior a 9, podemos ejecutar la siguiente 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

Al ejecutar la consulta anterior, podemos ver que solo coincide el registro de Alice:

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

Por lo tanto, la ventaja de utilizar los operadores SQL EXISTS y NOT EXISTS es que la ejecución de la subconsulta interna se puede detener siempre que se encuentre un registro coincidente.

Vlad Mihalcea
fuente
-1

Depende..

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

no sería relativamente lento, no hay mucho para limitar el tamaño de lo que la consulta verifica si la clave está activada. EXISTA sería preferible en este caso.

Pero, dependiendo del optimizador del DBMS, esto no podría ser diferente.

Como un ejemplo de cuándo EXISTE es mejor

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
fuente
1
INy EXISTS obtener el mismo plan en SQL Server . La pregunta es sobre NOT INvs de NOT EXISTStodos modos.
Martin Smith