Meilleure pratique entre utiliser LEFT JOIN ou NOT EXISTS

68

Existe-t-il une bonne pratique entre utiliser un format LEFT JOIN ou un format NOT EXISTS?

Quel est l'avantage d'utiliser l'un sur l'autre?

Si aucun, lequel devrait être préféré?

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)

J'utilise des requêtes dans Access sur une base de données SQL Server.

Michael Richardson
la source
2
En aparté, l'approche apparemment identique WHERE A.idx NOT IN (...) est pas identique en raison du comportement trivalent NULL(c. NULL-à-n'est pas égal à NULL(ni inégale), donc si vous avez une NULL en tableBvous obtiendrez des résultats inattendus!)
Elaskanator

Réponses:

58

La plus grande différence ne réside pas dans la jointure ni dans la jonction, elle est (comme écrit), la SELECT *.

Dans le premier exemple, vous obtenez toutes les colonnes des deux A et B, alors que dans le deuxième exemple, vous n’obtenez que des colonnes A.

Dans SQL Server, la deuxième variante est légèrement plus rapide dans un exemple très simple:

Créez deux exemples de tables:

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

Insérer 10 000 lignes dans chaque tableau:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Supprimer tous les 5 rangs de la deuxième table:

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;

Effectuez les deux SELECTvariantes d'instruction de test :

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

Plans d'exécution:

entrez la description de l'image ici

La deuxième variante n'a pas besoin d'effectuer l'opération de filtrage car elle peut utiliser l'opérateur anti-jointure gauche.

Max Vernon
la source
24

Logiquement, ils sont identiques, mais NOT EXISTSsont plus proches de l'AntiSemiJoin que vous demandez et sont généralement préférés. Cela met également en évidence le fait que vous ne pouvez pas accéder aux colonnes dans B, car il est uniquement utilisé comme filtre (au lieu de les avoir avec des valeurs NULL).

Il y a de nombreuses années (SQL Server 6.0 ish), LEFT JOINétait plus rapide, mais ce n'est pas le cas depuis très longtemps. Ces jours-ci, NOT EXISTSest légèrement plus rapide.


Le principal impact d’Access est que la JOINméthode doit terminer la jointure avant de la filtrer, en construisant l’ensemble joint en mémoire. Son utilisation NOT EXISTSvérifie la ligne mais n'attribue pas d'espace pour les colonnes. De plus, il cesse de chercher dès qu'il trouve une ligne. Les performances varient un peu plus dans Access, mais une règle générale est que cela a NOT EXISTStendance à être un peu plus rapide. Je serais moins enclin à dire que c'est la "meilleure pratique", car il y a plus de facteurs impliqués.

Rob Farley
la source
6

Une exception que j'ai remarquée à la NOT EXISTSsupériorité (même marginale) de l' LEFT JOIN ... WHERE IS NULLutilisation de serveurs liés .

En examinant les plans d'exécution, il apparaît que l' NOT EXISTSopérateur est exécuté de manière imbriquée en boucle. Pour cela, il est exécuté ligne par ligne (ce qui est logique, je suppose).

Exemple de plan d'exécution démontrant ce comportement: entrez la description de l'image ici

robopim
la source
1
Les serveurs liés sont brutaux pour ce genre de chose. Une approche possible pour résoudre ce problème consiste à copier les données distantes sur le lien du serveur lié en utilisant un simple INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=ypuis en exécutant la NOT EXISTS (...)clause sur cette copie temporaire de la base de données.
Max Vernon
2
Un peu timide en ce moment pour obtenir une réponse de Max Vernon sur mon post! Fanboy'ing à part. C'est drôle que vous mentionniez cela, car j'ai utilisé cette approche exacte à plusieurs reprises pour tirer le meilleur parti de ces situations multi-serveurs.
robopim
1
À la vôtre, @pimbrouwers - merci pour votre gentil commentaire!
Max Vernon
5

En général, le moteur créera un plan d'exécution basé essentiellement sur:

  1. Le nombre de lignes dans A et B
  2. S'il y a un index sur A et / ou B.
  3. Le nombre attendu de lignes de résultats (et de lignes intermédiaires)
  4. La forme de la requête d'entrée (c'est-à-dire votre question)

Pour 4):

Le plan "pas existe" encourage un plan basé sur la recherche sur la table B. C'est un bon choix lorsque la table A est petite et que la table B est grande (et qu'un index existe sur B).

Le plan "anti-jointure" est un bon choix lorsque la table A est très grande ou la table B est très petite ou pas d'index sur B et renvoie un jeu de résultats volumineux.

Cependant, il ne s'agit que d'un "encouragement", comme une entrée pondérée. Un fort (1), (2), (3) fait souvent le choix de (4) théorique.

(Ignorer l'effet de votre exemple renvoyant différentes colonnes en raison de la *, adressée par @MaxVernon answer.).

crokusek
la source