SQL: comment vérifier correctement si un enregistrement existe


207

En lisant de la documentation liée au réglage SQL, j'ai trouvé ceci:

SELECT COUNT(*) :

  • Compte le nombre de lignes.
  • Souvent utilisé de manière inappropriée pour vérifier l'existence d'un enregistrement.

Est SELECT COUNT(*) vraiment si mauvais?

Quelle est la bonne façon de vérifier l'existence d'un enregistrement?

Réponses:


255

Il est préférable d'utiliser l'un des éléments suivants:

-- Method 1.
SELECT 1
FROM table_name
WHERE unique_key = value;

-- Method 2.
SELECT COUNT(1)
FROM table_name
WHERE unique_key = value;

La première alternative ne devrait vous donner aucun résultat ou un résultat, le deuxième compte devrait être zéro ou un.

Quel âge a la documentation que vous utilisez? Bien que vous ayez lu de bons conseils, la plupart des optimiseurs de requêtes dans les SGBDR récents optimisent de SELECT COUNT(*)toute façon, donc bien qu'il y ait une différence en théorie (et dans les bases de données plus anciennes), vous ne devriez pas remarquer de différence dans la pratique.


1
Je préciserai que je voulais "clé unique" avec la clause "key = value" mais à part ça, je suis toujours derrière ma réponse.
Martin Schapendonk

1
D'ACCORD. Avec cette prémisse en effet, la requête ne retournerait qu'un ou zéro enregistrement. MAIS: La question ne se limite pas à une colonne unique. Aussi: le deuxième nombre de requêtes (1) est équivalent au nombre (*) d'un POV pratique.
Martin Ba

1
La question dit "quelle est la bonne façon de vérifier l'existence d'un enregistrement A". J'ai interprété cela comme singulier, comme dans: 1 enregistrement. La différence entre le compte (*) et le compte (1) est déjà couverte par ma réponse. Je préfère le compte (1) car il ne repose pas sur une implémentation spécifique du SGBDR.
Martin Schapendonk

192

Je préférerais ne pas utiliser du tout la fonction Count:

IF [NOT] EXISTS ( SELECT 1 FROM MyTable WHERE ... )
     <do smth>

Par exemple, si vous souhaitez vérifier si l'utilisateur existe avant de l'insérer dans la base de données, la requête peut ressembler à ceci:

IF NOT EXISTS ( SELECT 1 FROM Users WHERE FirstName = 'John' AND LastName = 'Smith' )
BEGIN
    INSERT INTO Users (FirstName, LastName) VALUES ('John', 'Smith')
END

Généralement, nous l'utilisons (la vérification) lorsque vous voulez faire quelque chose, alors votre réponse est plus complète.
Abner Escócio

Bon de mentionner qu'en utilisant T-SQL
Bronek

20

Vous pouvez utiliser:

SELECT 1 FROM MyTable WHERE <MyCondition>

S'il n'y a aucun enregistrement correspondant à la condition, le jeu d'enregistrements résultant est vide.


Voulez-vous dire TOP 1? -> (SÉLECTIONNEZ LE TOP 1 DE MyTable OERE <MyCondition>)
Jacob

6
Non, je voulais dire exactement "1"
Cătălin Pitiș

1
pour permettre à l'optimiseur de requêtes de savoir que vous ne lirez pas / n'aurez pas besoin des jeux de données restants, vous devez indiquer SELECT TOP 1 1 FROM ... WHERE ... (ou utiliser les conseils de requête appropriés pour votre RDBS)
eFloh

3
L'opérateur Exists lui-même essaie de récupérer le minimum absolu d'informations, donc l'ajout de TOP 1 ne fait rien sauf ajouter 5 caractères à la taille de la requête. - sqlservercentral.com/blogs/sqlinthewild/2011/04/05/…
AquaAlex

13

Les autres réponses sont assez bonnes, mais il serait également utile d'ajouter LIMIT 1(ou l'équivalent , pour éviter la vérification des lignes inutiles.


3
Si une requête "vérifier l'existence" renvoie plus d'une ligne, je pense qu'il est plus utile de revérifier votre clause WHERE au lieu de LIMITER le nombre de résultats.
Martin Schapendonk

2
Je pense que la limite est utilisée dans Oracle et non dans SQL Server
Shantanu Gupta

7
J'examine le cas où ils peuvent légitimement être plusieurs lignes - où la question est: "Y a-t-il (une ou plusieurs) lignes qui remplissent cette condition?" Dans ce cas, vous ne voulez pas les regarder tous, juste un.
JesseW

1
@Shantanu - Je sais, c'est pourquoi j'ai fait un lien vers l'article (très complet) en.wikipedia expliquant les autres formes.
JesseW

11
SELECT COUNT(1) FROM MyTable WHERE ...

fera une boucle à travers tous les enregistrements. C'est la raison pour laquelle il est mauvais d'utiliser pour l'existence d'un enregistrement.

j'utiliserais

SELECT TOP 1 * FROM MyTable WHERE ...

Après avoir trouvé 1 enregistrement, il terminera la boucle.


Dans le cas où SELECT TOP 1cela se terminera-t-il réellement après en avoir trouvé un ou continuera-t-il à tout trouver pour pouvoir dire lequel est TOP?
Eirik H

3
PS: pour être sûr que j'ai toujoursIF EXISTS (SELECT TOP 1 1 FROM ... WHERE ..)
Eirik H

l'opérateur Star forcera le SGBD à accéder à l'index clusterisé au lieu des seuls index qui seront nécessaires pour votre condition de jointure. il est donc préférable d'utiliser une valeur constante comme résultat, c'est-à-dire sélectionner top 1 1 .... Cela renverra 1 ou DB-Null, selon que la condition est une correspondance ou non.
eFloh

c'est bien. J'aime le premier.
isxaker

10

Vous pouvez utiliser:

SELECT COUNT(1) FROM MyTable WHERE ... 

ou

WHERE [NOT] EXISTS 
( SELECT 1 FROM MyTable WHERE ... )

Ce sera plus efficace que SELECT *puisque vous sélectionnez simplement la valeur 1 pour chaque ligne, plutôt que tous les champs.

Il y a aussi une différence subtile entre COUNT (*) et COUNT (nom de colonne):

  • COUNT(*) comptera toutes les lignes, y compris les valeurs nulles
  • COUNT(column name) ne comptera que les occurrences non nulles du nom de la colonne

2
Vous faites l'hypothèse erronée qu'un SGBD vérifiera en quelque sorte toutes ces colonnes. La différence de performances entre count(1)et count(*)ne sera différente que dans le SGBD le plus mort cérébral.
paxdiablo

2
Non, je dis que vous comptez sur les détails de mise en œuvre lorsque vous déclarez que ce sera plus efficace. Si vous voulez vraiment vous assurer d'obtenir les meilleures performances, vous devez le profiler pour l'implémentation spécifique en utilisant des données représentatives, ou tout simplement l'oublier complètement. Tout le reste est potentiellement trompeur et pourrait changer radicalement lors du passage (par exemple) de DB2 à MySQL.
paxdiablo

1
Je tiens à préciser que je ne conteste pas votre réponse. Il est utile. Le seul problème avec lequel je m'oppose est l'allégation d'efficacité, car nous avons effectué des évaluations dans DB2 / z et constaté qu'il n'y avait pas de réelle différence entre count(*)et count(1). Que ce soit le cas pour d' autres SGBD, je ne peux pas le dire.
paxdiablo

3
"Tout le reste est potentiellement trompeur et pourrait changer radicalement lors du déplacement (par exemple) de DB2 vers MySQL" Vous êtes beaucoup plus susceptible d'être mordu par la dégradation des performances de SELECT COUNT (*) lors du déplacement du SGBD qu'une différence d'implémentation dans SELECT 1 ou COUNT (1). Je suis fermement convaincu de l'écriture du code qui exprime le plus clairement exactement ce que vous voulez réaliser, plutôt que de compter sur des optimiseurs ou des compilateurs pour adopter par défaut le comportement souhaité.
Winston Smith

1
La déclaration trompeuse "COUNT (*)" signifie "compter les lignes". Il ne nécessite pas d'accès à une colonne particulière. Et dans la plupart des cas, il ne sera même pas nécessaire d'accéder à la ligne elle-même, car un nombre d'index unique suffit.
James Anderson

9

Vous pouvez utiliser:

SELECT 1 FROM MyTable WHERE... LIMIT 1

Utilisez select 1pour empêcher la vérification des champs inutiles.

Utilisez LIMIT 1 pour empêcher la vérification des lignes inutiles.


3
Bon point mais Limit fonctionne sur MySQL et PostgreSQL, les meilleurs travaux sur SQL Server, vous devriez le noter sur votre réponse
Leo Gurdian

0

J'utilise de cette façon:

IIF(EXISTS (SELECT TOP 1 1 
                FROM Users 
                WHERE FirstName = 'John'), 1, 0) AS DoesJohnExist

0

Autre option:

SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM [MyTable] AS [MyRecord])
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.