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

38

Chaque fois que je dois vérifier l'existence d'une ligne dans une table, j'ai tendance à toujours écrire une condition telle que:

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
       )

Certaines personnes écrivent comme ceci:

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 EXISTSremplacée par EXISTS: Dans certains cas, je peux l'écrire avec une LEFT JOINcondition supplémentaire (parfois appelée antijointure ):

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'essaie de l'éviter parce que je pense que le sens est moins clair, en particulier lorsque ce qui vous primary_keymanque n'est pas évident, ou lorsque votre clé primaire ou votre condition de jointure est multi-colonne (et vous pouvez facilement oublier l'une des colonnes). Cependant, vous conservez parfois 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 à la SELECT 1place SELECT *?
    Existe-t-il des cas où cela ne se comporte pas de la même manière?

  2. Bien que ce que j’ai écrit soit le code SQL (AFAIK) standard: existe-t-il une telle différence pour différentes bases de données / versions antérieures?

  3. Y-a-t-il un avantage à écrire explicitement un anti-jointure?
    Les planificateurs / optimistes contemporains le traitent-ils différemment de la NOT EXISTSclause?

joanolo
la source
5
Notez que PostgreSQL supporte les sélections sans colonnes, vous pouvez donc écrire EXISTS (SELECT FROM ...).
Droitier
1
Je posais presque la même question à SO il y a quelques années: stackoverflow.com/questions/7710153/…
Erwin Brandstetter

Réponses:

45

Non, il n'y a pas de différence d'efficacité entre (NOT) EXISTS (SELECT 1 ...)et (NOT) EXISTS (SELECT * ...)dans tous les principaux SGBD. J'ai souvent vu (NOT) EXISTS (SELECT NULL ...)être utilisé aussi.

Dans certains cas, vous pouvez même écrire (NOT) EXISTS (SELECT 1/0 ...)et le résultat est le même - sans erreur (division par zéro), ce qui prouve que l'expression n'y est même pas évaluée.


A propos de la LEFT JOIN / IS NULLméthode anti - jointure, une correction: cela équivaut à NOT EXISTS (SELECT ...).

Dans ce cas, NOT EXISTSvsLEFT JOIN / IS NULL, vous pouvez obtenir différents plans d’exécution. Dans MySQL par exemple et surtout dans les versions antérieures (antérieures à la version 5.7), les plans seraient assez similaires mais non identiques. Les optimiseurs d'autres SGBD (SQL Server, Oracle, Postgres, DB2) sont, à ma connaissance, plus ou moins capables de réécrire ces 2 méthodes et d'envisager les mêmes plans pour les deux. Néanmoins, il n’existe pas de telle garantie et lors de l’optimisation, il est bon de vérifier les plans de différentes réécritures équivalentes, dans la mesure où il se peut que chaque optimiseur ne réécrit pas (par exemple, des requêtes complexes, avec de nombreuses jointures et / ou des tables dérivées / les sous-requêtes à l'intérieur de la sous-requête, où les conditions de plusieurs tables, les colonnes composites utilisées dans les conditions de jointure) ou les choix et les plans de l'optimiseur sont affectés différemment par les index, les paramètres disponibles, etc.

Notez également que USINGvous ne pouvez pas utiliser tous les SGBD (SQL Server par exemple). Le plus commun JOIN ... ONfonctionne partout.
Et les colonnes doivent être préfixées avec le nom de la table / alias dans le SELECTpour éviter les erreurs / ambiguïtés lorsque nous avons des jointures.
Je préfère également généralement mettre la colonne jointe dans la IS NULLvérification (bien que la clé PK ou toute colonne non nullable serait OK, cela pourrait être utile pour l'efficacité lorsque le plan LEFT JOINutilise un index non clusterisé):

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

Il existe également une troisième méthode pour les anti-jointures, NOT INmais elle utilise une sémantique (et des résultats!) Différents si la colonne de la table interne est nullable. Il peut être utilisé en excluant les lignes NULL, rendant la requête équivalente aux 2 versions précédentes:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Cela donne aussi généralement des plans similaires dans la plupart des SGBD.

ypercubeᵀᴹ
la source
1
Jusqu'aux versions très récentes de MySQL [NOT] IN (SELECT ...), bien qu'équivalent, ses performances étaient très mauvaises. L'éviter!
Rick James
4
Ce n'est pas vrai pour PostgreSQL . SELECT *fait certainement plus de travail. Par souci de simplicité, je vous conseillerais d'utiliserSELECT 1
Evan Carroll,
11

Il existe une catégorie de cas où SELECT 1et SELECT *qui ne sont pas interchangeables - plus précisément, l'un sera toujours accepté dans ces cas, tandis que l'autre ne le sera généralement pas.

Je parle de cas où vous devez vérifier l'existence de lignes d'un ensemble groupé . Si table Tcontient des colonnes C1et C2que vous vérifiez l'existence de groupes de lignes correspondant à une condition spécifique, vous pouvez utiliser la SELECT 1méthode suivante:

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.

C'est simplement 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 l' autre réponse .

Notes complémentaires à la suite des commentaires

Il semble que peu de produits de base de données supportent cette distinction. Des produits tels que SQL Server, Oracle, MySQL et SQLite accepteront volontiers SELECT *la requête ci-dessus sans erreur, ce qui signifie probablement qu'ils traitent un EXISTS SELECTde manière particulière.

PostgreSQL est un SGBDR sur lequel le système SELECT *peut échouer, mais peut toujours fonctionner dans certains cas. En particulier, si vous regroupez par PK, SELECT *tout fonctionnera correctement, sinon le message:

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

Andriy M
la source
1
Bons points, bien que ce ne soit pas exactement le cas qui m'inquiétait. Celui-ci montre une différence conceptuelle . Parce que, quand vous GROUP BY, le concept de *est sans signification (ou, du moins, pas si clair).
joanolo
5

Une façon sans doute intéressante de réécrire la EXISTSclause qui aboutit à une requête plus claire et peut-être moins trompeuse, du moins dans SQL Server, serait la suivante:

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

La version anti-semi-jointure de cela 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 sur le même plan que WHERE EXISTSou WHERE NOT EXISTS, mais l'intention est indéniable, et vous n'avez pas d '"étrange" 1ou *.

Fait intéressant, les problèmes de contrôle nul associés à NOT IN (...)sont problématiques pour <> ALL (...), alors qu'ils NOT EXISTS (...)ne souffrent pas de ce problème. Considérez les deux tables suivantes 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 allons ajouter des données aux deux, avec des lignes qui correspondent et d'autres qui ne le sont 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 NOT IN (...)requête:

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

A le plan suivant:

entrez la description de l'image ici

La requête ne renvoie aucune ligne car les valeurs NULL rendent impossible la confirmation de l'égalité.

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

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

entrez la description de l'image ici

La variante using NOT EXISTS (...)affiche 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:

entrez la description de l'image ici

Les résultats de cette requête:

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

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

Max Vernon
la source
3
Je dois dire que je ne trouve *pas étrange: je lis EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... Quoi qu'il en soit, j'aime bien avoir des alternatives, et la vôtre est clairement lisible. Un doute / une mise en garde: comment va-t-il se comporter si best annulable? [J'ai eu de mauvaises expériences et quelques nuits courtes en essayant de découvrir un méfait causé par un x IN (SELECT something_nullable FROM a_table)]
joanolo
EXISTS vous indique si une table a une ligne et renvoie true ou false. EXISTS (SELECT x FROM (valeurs (null)) est vrai. IN est = N'IMPORTE. & NON IN est <> TOUT. Ces 4 prennent une ligne RHS avec des valeurs NULL pouvant correspondre. (X) = ANY (valeurs (null)) & (x) <> ALL (valeurs (null)) sont inconnus / nuls mais EXISTS (valeurs (null)) est vraies. (IN & = ANY ont le même problème de vérification NULL associé à NOT IN (...) [& ] <> ALL (...) ". TOUT & TOUT itérer OR & AND. Mais il n'y a que des" problèmes "si vous n'organisez pas la sémantique comme prévu.) Ne conseillez pas de les utiliser pour EXISTS. Ils sont trompeurs. , pas "moins trompeur".
philipxy
@philliprxy - Si je me trompe, je n'ai aucun problème à l'admettre. N'hésitez pas à ajouter votre propre réponse si vous en avez envie.
Max Vernon le
4

La "preuve" qu'ils sont identiques (en 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 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 intéressant de comparer EXISTSvs anti-jointures. Votre version peut faire un meilleur travail avec l'un par rapport à l'autre.

Rick James
la source
4

Dans certaines bases de données, cette optimisation ne fonctionne pas encore. Comme par exemple dans PostgreSQL La version 9.6 é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 ça va réussir.

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

C'est un échec parce que ce qui suit échoue, mais cela signifie qu'il y a toujours une différence.

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

Vous pouvez trouver plus d'informations sur ce caprice particulier et la violation de la spécification dans la réponse à la question suivante: La spécification SQL requiert-elle un GROUP BY dans EXISTS ()

Evan Carroll
la source
Un coin rare, un peu bizarre peut-être, mais encore une fois, la preuve qu'il faut faire beaucoup de compromis lors de la conception d'une base de données ...
joanolo
-1

J'ai toujours utilisé select top 1 'x'(SQL Server)

Théoriquement, select top 1 'x'serait plus efficace que select *, puisque le premier serait complet après avoir sélectionné une constante sur l’existence d’une ligne qualificative, alors que le second sélectionnerait tout.

CEPENDANT, même si cela a très tôt été pertinent, l’optimisation a rendu la différence non pertinente dans probablement tous les principaux RDBS.

G DeMasters
la source
Logique. Cela pourrait être (ou aurait pu être) l’un des rares cas où top nsans order bysont une bonne idée.
joanolo
3
"Théoriquement, ...." Non, théoriquement, select top 1 'x'ne devrait pas être plus efficace que select *dans une Existexpression. En pratique, il peut être plus efficace si l'optimiseur fonctionne de manière sous-optimale mais, théoriquement, les deux expressions sont équivalentes.
miracle173
-4

IF EXISTS(SELECT TOP(1) 1 FROMest une meilleure habitude à long terme et sur toutes les plateformes simplement parce que vous n’avez même pas besoin de vous inquiéter de la qualité de votre plate-forme / version actuelle; et SQL va de TOP nparamétrable TOP(n). Cela devrait être une compétence unique.

ajeh
la source
3
Que voulez-vous dire par "à travers les plates-formes" ? TOPn'est même pas valide SQL.
Ypercubeᵀᴹ
"SQL se déplace .." est tout simplement faux. Il n'y a pas TOP (n)de "SQL" - le langage de requête standard. Il y en a un sur T-SQL qui est le dialecte utilisé par Microsoft SQL Server.
a_horse_with_no_name
La balise sur la question d'origine est "SQL Server". Mais il est acceptable de voter et de contester ce que j'ai dit - le but de ce site est de permettre un vote facile. Qui suis-je pour faire pleuvoir sur votre parade avec une attention ennuyeuse aux détails?
ajeh