web-dev-qa-db-fra.com

EXISTS (SELECT 1 ...) vs EXISTS (SELECT * ...) L'un ou l'autre?

Chaque fois que j'ai besoin de vérifier l'existence d'une ligne dans un tableau, j'ai tendance à écrire toujours une condition comme:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

D'autres personnes l'écrivent comme:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This Nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Lorsque la condition est NOT EXISTS au lieu de EXISTS: Dans certains cas, je pourrais l'écrire avec un LEFT JOIN et une condition supplémentaire (parfois appelée antijoin ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

J'essaye de l'éviter car je pense que le sens est moins clair, surtout quand quel est votre primary_key n'est pas si évident, ou lorsque votre clé primaire ou votre condition de jointure est multi-colonnes (et vous pouvez facilement oublier l'une des colonnes). Cependant, parfois vous maintenez du code écrit par quelqu'un d'autre ... et il est juste là.

  1. Y a-t-il une différence (autre que le style) à utiliser SELECT 1 au lieu de SELECT *?
    Y a-t-il un cas d'angle où il ne se comporte pas de la même manière?

  2. Bien que ce que j'ai écrit soit un standard SQL (AFAIK): y a-t-il une telle différence pour différentes bases de données/versions plus anciennes?

  3. Y a-t-il un avantage à expliquer explicitement une antijoin?
    Les planificateurs/optimiseurs contemporains le traitent-ils différemment du NOT EXISTS clause?

42
joanolo

Il existe une catégorie de cas où SELECT 1 et SELECT * ne sont pas interchangeables - plus précisément, l'une sera toujours acceptée dans ces cas, tandis que la plupart ne le seront pas.

Je parle de cas où vous devez vérifier l'existence de lignes d'un ensemble groupé . Si la table T contient des colonnes C1 et C2 et vous vérifiez l'existence de groupes de lignes qui correspondent à une condition spécifique, vous pouvez utiliser SELECT 1 comme ça:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

mais vous ne pouvez pas utiliser SELECT * de la même manière.

Ce n'est qu'un aspect syntaxique. Lorsque les deux options sont acceptées syntaxiquement, vous n'aurez probablement aucune différence en termes de performances ou de résultats renvoyés, comme cela a été expliqué dans autre réponse .

Notes supplémentaires à la suite des commentaires

Il semble que peu de produits de base de données prennent en charge cette distinction. Des produits comme SQL Server, Oracle, MySQL et SQLite accepteront avec plaisir SELECT * dans la requête ci-dessus sans aucune erreur, ce qui signifie probablement qu'ils traitent un EXISTS SELECT d'une manière spéciale.

PostgreSQL est un SGBDR où SELECT * peut échouer, mais peut toujours fonctionner dans certains cas. En particulier, si vous regroupez par PK, SELECT * fonctionnera correctement, sinon il échouera avec le message:

ERREUR: la colonne "T.C2" doit apparaître dans la clause GROUP BY ou être utilisée dans une fonction d'agrégation

11
Andriy M

Une façon sans doute intéressante de réécrire la clause EXISTS qui se traduit par une requête plus propre et peut-être moins trompeuse, au moins dans SQL Server, serait:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

La version anti-semi-jointure ressemblerait à ceci:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Les deux sont généralement optimisés pour le même plan que WHERE EXISTS Ou WHERE NOT EXISTS, Mais l'intention est sans équivoque, et vous n'avez aucun "étrange" 1 Ou *.

Fait intéressant, les problèmes de vérification nulle associés à NOT IN (...) sont problématiques pour <> ALL (...), tandis que NOT EXISTS (...) ne souffre pas de ce problème. Considérez les deux tableaux suivants avec une colonne nullable:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Nous ajouterons des données aux deux, avec des lignes qui correspondent et d'autres qui ne le font pas:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- + 
 | ID | SomeValue | 
 + -------- + ----------- + 
 | 1 | 1 | 
 | 2 | 2 | 
 | 3 | 3 | 
 | 4 | NULL | 
 + -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- + 
 | ID | SomeValue | 
 + -------- + ----------- + 
 | 1 | 1 | 
 | 2 | 2 | 
 | 3 | NULL | 
 | 4 | 4 | 
 + -------- + ----------- +

La requête NOT IN (...):

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

A le plan suivant:

enter image description here

La requête ne renvoie aucune ligne car les valeurs NULL rendent l'égalité impossible à confirmer.

Cette requête, avec <> ALL (...) montre le même plan et ne renvoie aucune ligne:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

enter image description here

La variante utilisant NOT EXISTS (...), montre une forme de plan légèrement différente et renvoie des lignes:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

Le plan:

enter image description here

Les résultats de cette requête:

+ -------- + ----------- + 
 | ID | SomeValue | 
 + -------- + ----------- + 
 | 3 | 3 | 
 | 4 | NULL | 
 + -------- + ----------- +

Cela rend l'utilisation de <> ALL (...) tout aussi sujette à des résultats problématiques que NOT IN (...).

5
Max Vernon

La "preuve" qu'ils sont identiques (dans MySQL) est à faire

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

puis répétez avec SELECT 1. Dans les deux cas, la sortie "étendue" montre qu'elle a été transformée en SELECT 1.

De même, COUNT(*) est transformé en COUNT(0).

Autre chose à noter: des améliorations d'optimisation ont été apportées dans les versions récentes. Il peut être utile de comparer EXISTS aux anti-jointures. Votre version peut faire un meilleur travail avec l'un par rapport à l'autre.

4
Rick James

Dans certaines bases de données, cette optimisation ne fonctionne pas encore. comme par exemple dans PostgreSQL À partir de la version 9.6, cela échouera.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Et cela réussira.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Il échoue car ce qui suit échoue, mais cela signifie toujours qu'il y a une différence.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Vous pouvez trouver plus d'informations sur cette bizarrerie particulière et la violation de la spécification dans ma réponse à la question, La spécification SQL nécessite-t-elle un GROUP BY dans EXISTS ()

4
Evan Carroll