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à.
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?
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?
Y a-t-il un avantage à expliquer explicitement une antijoin?
Les planificateurs/optimiseurs contemporains le traitent-ils différemment du NOT EXISTS
clause?
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 .
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
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:
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
);
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:
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 (...)
.
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.
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 ()