La mejor práctica entre usar JOIN IZQUIERDO o NO EXISTE

67

¿Existe alguna práctica recomendada entre el uso de un formato LEFT JOIN o NOT EXISTS?

¿Cuál es el beneficio de usar uno sobre el otro?

Si no hay ninguno, ¿cuál debería preferirse?

SELECT *
FROM tableA A
LEFT JOIN tableB B
     ON A.idx = B.idx
WHERE B.idx IS NULL

SELECT *
FROM tableA A
WHERE NOT EXISTS
(SELECT idx FROM tableB B WHERE B.idx = A.idx)

Estoy usando consultas dentro de Access contra una base de datos de SQL Server.

Michael Richardson
fuente
2
Como acotación al margen, el enfoque aparentemente idénticos WHERE A.idx NOT IN (...) es no idéntica , debido al comportamiento de trivalente NULL(es decir, NULLno es igual a NULL(ni desigual), por lo tanto, si usted tiene cualquiera NULL de tableBobtendrá resultados inesperados!)
Elaskanator

Respuestas:

58

La mayor diferencia no está en la combinación vs no existe, es (como está escrito), el SELECT *.

En el primer ejemplo, obtienes todas las columnas de ambos A y B, mientras que en el segundo ejemplo, obtienes solo columnas de A.

En SQL Server, la segunda variante es ligeramente más rápida en un ejemplo artificial muy simple:

Crea dos tablas de muestra:

CREATE TABLE dbo.A
(
    A_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);

CREATE TABLE dbo.B
(
    B_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);
GO

Inserte 10,000 filas en cada tabla:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Eliminar cada quinta fila de la segunda tabla:

DELETE 
FROM dbo.B 
WHERE B_ID % 5 = 1;

SELECT COUNT(*) -- shows 10,000
FROM dbo.A;

SELECT COUNT(*) -- shows  8,000
FROM dbo.B;

Realice las dos SELECTvariantes de declaración de prueba :

SELECT *
FROM dbo.A
    LEFT JOIN dbo.B ON A.A_ID = B.B_ID
WHERE B.B_ID IS NULL;

SELECT *
FROM dbo.A
WHERE NOT EXISTS (SELECT 1
    FROM dbo.B
    WHERE b.B_ID = a.A_ID);

Planes de ejecución:

ingrese la descripción de la imagen aquí

La segunda variante no necesita realizar la operación de filtro ya que puede usar el operador izquierdo anti-semi-unión.

Max Vernon
fuente
23

Lógicamente son idénticos, pero NOT EXISTSestá más cerca del AntiSemiJoin que está solicitando, y generalmente se prefiere. También resalta mejor que no puede acceder a las columnas en B, porque solo se usa como un filtro (en lugar de tenerlas disponibles con valores NULL).

Hace muchos años (SQL Server 6.0 ish), LEFT JOINfue más rápido, pero ese no ha sido el caso durante mucho tiempo. En estos días, NOT EXISTSes marginalmente más rápido.


El mayor impacto en Access es que el JOINmétodo tiene que completar la unión antes de filtrarla, construyendo el conjunto unido en la memoria. Usarlo NOT EXISTSverifica la fila pero no asigna espacio para las columnas. Además, deja de buscar una vez que encuentra una fila. El rendimiento varía un poco más en Access, pero una regla general es que NOT EXISTStiende a ser un poco más rápido. Me inclinaría menos a decir que es "la mejor práctica", ya que hay más factores involucrados.

Rob Farley
fuente
6

Una excepción que he notado por NOT EXISTSser superior (aunque marginalmente) LEFT JOIN ... WHERE IS NULLes cuando utilizo servidores vinculados .

Al examinar los planes de ejecución, parece que el NOT EXISTSoperador se ejecuta en forma de bucle anidado. Por el cual se ejecuta por fila (lo que supongo que tiene sentido).

Ejemplo de plan de ejecución que demuestra este comportamiento: ingrese la descripción de la imagen aquí

robopim
fuente
1
Los servidores vinculados son brutales para ese tipo de cosas. Un posible enfoque para resolver ese problema es copiar los datos remotos a través del enlace del servidor vinculado usando una cláusula simple y INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yluego ejecutando NOT EXISTS (...)esa copia temporal de la base de datos.
Max Vernon
2
¡Un poco tímido en este momento para obtener una respuesta de Max Vernon en mi publicación! Fanboy'ing a un lado. Es curioso que mencione eso, ya que he usado ese enfoque exacto en varias ocasiones para aprovechar al máximo esas situaciones entre servidores.
robopim
1
Saludos, @pimbrouwers: ¡gracias por su amable comentario!
Max Vernon
5

En general, el motor creará un plan de ejecución basado esencialmente en:

  1. El número de filas en A y B
  2. Si hay un índice en A y / o B.
  3. El número esperado de filas de resultados (y filas intermedias)
  4. La forma de la consulta de entrada (es decir, su pregunta)

Para 4):

El plan "no existe" alienta un plan basado en la búsqueda en la tabla B. Esta es una buena opción cuando la tabla A es pequeña y la tabla B es grande (y existe un índice en B).

El plan "anti-unión" es una buena opción cuando la tabla A es muy grande o la tabla B es muy pequeña o no hay índice en B y devuelve un gran conjunto de resultados.

Sin embargo, es solo un "estímulo", como una entrada ponderada. Un fuerte (1), (2), (3) a menudo hace la elección de (4) discutible.

(Ignorando el efecto de su ejemplo que devuelve diferentes columnas debido al *, abordado por la respuesta @MaxVernon).

crokusek
fuente