web-dev-qa-db-fra.com

Meilleure pratique entre l'utilisation de LEFT JOIN ou NOT EXISTS

Existe-t-il une meilleure pratique entre l'utilisation d'un LEFT JOIN ou d'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 par rapport à une base de données SQL Server.

72
Michael Richardson

La plus grande différence n'est pas dans la jointure vs n'existe pas, c'est (comme écrit), le SELECT *.

Dans le premier exemple, vous obtenez toutes les colonnes de à la fois A et B, tandis que dans le deuxième exemple, vous obtenir uniquement les colonnes de A.

Dans SQL Server, la deuxième variante est légèrement plus rapide dans un exemple artificiel 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érez 10 000 lignes dans chaque table:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Supprimer tous les 5 rangs du deuxième tableau:

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;

Exécutez les deux variantes de test SELECT:

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:

enter image description here

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.

60
Max Vernon

Logiquement, ils sont identiques, mais NOT EXISTS est plus proche de l'AntiSemiJoin que vous demandez et est généralement préféré. Il souligne également mieux que vous ne pouvez pas accéder aux colonnes en B, car il est uniquement utilisé comme filtre (par opposition à leur disponibilité avec des valeurs NULL).

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


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

25
Rob Farley

Une exception que j'ai remarquée au NOT EXISTS étant supérieur (mais marginalement) à LEFT JOIN ... WHERE IS NULL est lorsque vous utilisez les serveurs liés .

D'après l'examen des plans d'exécution, il apparaît que NOT EXISTS L'opérateur est exécuté en boucle imbriquée. Ainsi, il est exécuté sur une base par ligne (ce qui, je suppose, est logique).

Exemple de plan d'exécution illustrant ce comportement: enter image description here

7
pimbrouwers

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 existe un index sur A et/ou B.
  3. Le nombre attendu de lignes de résultat (et de lignes intermédiaires)
  4. La forme de la requête d'entrée (c'est-à-dire votre question)

Pour 4):

Le plan "n'existe pas" encourage un plan basé sur la recherche sur le tableau B. C'est un bon choix lorsque le tableau A est petit et le tableau B est grand (et qu'un indice 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 aucun index sur B et retourne un ensemble de résultats important.

Cependant, ce n'est qu'un "encouragement", comme une entrée pondérée. Un fort (1), (2), (3) fait souvent le choix du (4) sans objet.

(Ignorant l'effet de votre exemple renvoyant différentes colonnes en raison du *, adressé par la réponse @MaxVernon.).

5
crokusek