Count (*) vs Count (1) - SQL Server

738

Vous vous demandez simplement si certains d'entre vous utilisent Count(1)trop Count(*)et s'il y a une différence notable dans les performances ou si c'est juste une habitude héritée qui a été avancée depuis des jours passés?

La base de données spécifique est SQL Server 2005.

super9
la source
7
Je ne connais pas SQL Server mais dans MySQL il n'y a pas de différence. COUNT (colonne) d'autre part est différent
Greg
118
Pas vrai. COUNT (SomeColumn) renverra uniquement le nombre de lignes contenant des valeurs non nulles pour SomeColumn. COUNT (*) et COUNT ('Foo') renverront le nombre total de lignes dans le tableau.
Steve Broberg
4
Wow Steve et j'étais là depuis 5 ans dans TSQL sans connaître le nombre (*) vs le nombre (ColumnName). Merci
Harindaka
3
Notez également les réponses à COUNT(*)vs COUNT(1)vs COUNT(pk)- quelle est la meilleure? . Il y a aussi COUNT(*)vs COUNT(column-name)- qui est plus correct? . Il pourrait bien y avoir d'autres doublons.
Jonathan Leffler

Réponses:

598

Il n'y a pas de différence.

Raison:

Livres en ligne dit " COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )"

"1" est une expression non nulle: c'est donc la même chose que COUNT(*). L'optimiseur le reconnaît pour ce qu'il est: trivial.

Identique à EXISTS (SELECT * ...ouEXISTS (SELECT 1 ...

Exemple:

SELECT COUNT(1) FROM dbo.tab800krows
SELECT COUNT(1),FKID FROM dbo.tab800krows GROUP BY FKID

SELECT COUNT(*) FROM dbo.tab800krows
SELECT COUNT(*),FKID FROM dbo.tab800krows GROUP BY FKID

Même IO, même plan, les travaux

Edit, août 2011

Question similaire sur DBA.SE .

Edit, déc.2011

COUNT(*)est mentionné spécifiquement dans ANSI-92 (recherchez " Scalar expressions 125")

Cas:

a) Si COUNT (*) est spécifié, alors le résultat est la cardinalité de T.

Autrement dit, la norme ANSI le reconnaît comme saignant ce que vous voulez dire. COUNT(1)a été optimisé par les fournisseurs de SGBDR en raison de cette superstition. Sinon, il serait évalué selon ANSI

b) Sinon, soit TX la table à une seule colonne résultant de l'application de l '<expression de valeur> à chaque ligne de T et de l'élimination des valeurs nulles. Si une ou plusieurs valeurs nulles sont éliminées, alors une condition de fin est levée: avertissement-

gbn
la source
73

Dans SQL Server, ces instructions produisent les mêmes plans.

Contrairement à l'opinion populaire, ils le font aussi dans Oracle.

SYS_GUID() dans Oracle est une fonction assez intensive en calcul.

Dans ma base de données de test, t_evenest une table avec des 1,000,000lignes

Cette requête:

SELECT  COUNT(SYS_GUID())
FROM    t_even

s'exécute pendant 48quelques secondes, car la fonction doit évaluer chaque SYS_GUID()retour pour s'assurer qu'il ne s'agit pas d'un NULL.

Cependant, cette requête:

SELECT  COUNT(*)
FROM    (
        SELECT  SYS_GUID()
        FROM    t_even
        )

ne fonctionne que pendant 2quelques secondes, car il n'essaie même pas d'évaluer SYS_GUID()(bien qu'il *soit un argument pour COUNT(*))

Quassnoi
la source
il devrait évaluer SYS_GUID()au moins (je veux dire exactement) une fois pour que la sous-requête renvoie le résultat, non?
asgs
@asgs: pourquoi le pensez-vous? Comment COUNT(*)dépend-on des valeurs de SYS_GUID?
Quassnoi
maintenant que vous demandez, je ne suis pas sûr. J'ai pensé COUNT(*)à exécuter, il a besoin d'une table, donc la sous-requête devrait agir comme telle. Sinon, je ne vois pas de moyen COUNT(*)de retourner une valeur significative
asgs
1
@asgs: en supposant que vous savez ce que fait la mapméthode, voyez-vous comment ces deux expressions: t_even.map(() => sys_guid()).lengthet t_even.lengthrenverraient toujours la même valeur? L'optimiseur d'Oracle est suffisamment intelligent pour le voir et optimiser la mappièce.
Quassnoi
1
@asgs exactement. Une correction mineure: lengthne dépend pas tout à fait ce que la collection se compose, juste sur le nombre de ses éléments. Si ce numéro est stocké dans les métadonnées de la collection (ce n'est pas le cas pour Oracle ou la plupart des autres SGBDR modernes mais c'est le cas pour l'ancien moteur de stockage de MySQL, MyISAM), il COUNT(*)suffirait alors de prendre la valeur des métadonnées.
Quassnoi
65

De toute évidence, COUNT(*)et COUNT(1)sera toujours le même résultat. Par conséquent, si l'un était plus lent que l'autre, cela serait effectivement dû à un bug d'optimisation. Étant donné que les deux formulaires sont utilisés très fréquemment dans les requêtes, cela n'aurait aucun sens pour un SGBD de permettre à un tel bogue de rester non corrigé. Par conséquent, vous constaterez que les performances des deux formulaires sont (probablement) identiques dans tous les principaux SGBD SQL.

Tony Andrews
la source
Je ne considérerais pas cela comme un bug si count (1) était plus lent que count (*). Si vous demandez aux dbms de générer des 1 et de compter ceux qui ne sont pas nuls, alors oui, cela se résume au nombre d'enregistrements, mais vous ne pouvez pas vous attendre à ce que les dbms détectent chaque non-sens que vous écrivez et le contournent pour vous.
Thorsten Kettner
1
Eh bien, un optimiseur est destiné à optimiser et pour un comptage il n'y a que 2 cas à considérer: expression qui peut être nulle, expression qui ne sera jamais nulle: le compte (1) tombe dans ce dernier donc il n'y a pas besoin pour le SGBD de "générer" 1 pour répondre à la question. (BTW je n'utiliserais jamais autre chose que compter (*), juste pour des raisons esthétiques.)
Tony Andrews
46

Je travaille sur l'équipe SQL Server et je peux, espérons-le, clarifier quelques points dans ce fil (je ne l'avais pas vu auparavant, donc je suis désolé que l'équipe d'ingénierie ne l'ait pas fait auparavant).

Tout d' abord, il n'y a pas de différence sémantique entre en select count(1) from tablefonction select count(*) from table. Ils retournent les mêmes résultats dans tous les cas (et c'est un bug sinon). Comme indiqué dans les autres réponses, select count(column) from tableest sémantiquement différent et ne renvoie pas toujours les mêmes résultats que count(*).

Deuxièmement, en ce qui concerne les performances, deux aspects importent dans SQL Server (et SQL Azure): le travail au moment de la compilation et le travail au moment de l'exécution. Le travail de temps de compilation est une quantité de travail supplémentaire insignifiante dans l'implémentation actuelle. Dans certains cas, il y a une extension de * à toutes les colonnes suivie d'une réduction à 1 colonne en raison de la façon dont certaines des opérations internes fonctionnent dans la liaison et l'optimisation. Je doute que cela apparaisse dans n'importe quel test mesurable et se perdrait probablement dans le bruit de toutes les autres choses qui se produisent sous les couvertures (telles que les statistiques automatiques, les sessions xevent, les frais généraux du magasin de requêtes, les déclencheurs, etc.). Il s'agit peut-être de quelques milliers d'instructions CPU supplémentaires. Donc, count (1) fait un tout petit peu moins de travail pendant la compilation (ce qui se produit généralement une fois et le plan est mis en cache sur plusieurs exécutions ultérieures). Pour le temps d'exécution, en supposant que les plans sont les mêmes, il ne devrait pas y avoir de différence mesurable. (L'un des exemples précédents montre une différence - il est probablement dû à d'autres facteurs sur la machine si le plan est le même).

Quant à savoir comment le plan peut être différent. Il est extrêmement peu probable que cela se produise, mais cela est potentiellement possible dans l'architecture de l'optimiseur actuel. L'optimiseur de SQL Server fonctionne comme un programme de recherche (pensez: programme informatique jouant aux échecs en cherchant à travers différentes alternatives pour différentes parties de la requête et en évaluant les alternatives pour trouver le plan le moins cher dans un délai raisonnable). Cette recherche a quelques limites sur la façon dont elle fonctionne pour que la compilation des requêtes se termine dans un délai raisonnable. Pour les requêtes au-delà des plus triviales, il existe des phases de la recherche et elles traitent des tranches de requêtes basées sur le coût que l'optimiseur pense que la requête doit potentiellement exécuter. Il y a 3 phases de recherche principales, et chaque phase peut exécuter des heuristiques plus agressives (coûteuses) en essayant de trouver un plan moins cher que n'importe quelle solution antérieure. En fin de compte, il y a un processus de décision à la fin de chaque phase qui essaie de déterminer s'il doit retourner le plan qu'il a trouvé jusqu'à présent ou s'il doit continuer à chercher. Ce processus utilise le temps total pris jusqu'à présent par rapport au coût estimé du meilleur plan trouvé jusqu'à présent. Ainsi, sur différentes machines avec différentes vitesses de CPU, il est possible (bien que rare) d'obtenir des plans différents en raison du dépassement du délai dans une phase antérieure avec un plan par rapport à la poursuite de la phase de recherche suivante. Il existe également quelques scénarios similaires liés à la temporisation de la dernière phase et potentiellement à un manque de mémoire sur des requêtes très, très coûteuses qui consomment toute la mémoire de la machine (ce qui n'est généralement pas un problème sur 64 bits mais c'était une préoccupation plus importante sur des serveurs 32 bits). En fin de compte, si vous obtenez un plan différent, les performances au moment de l'exécution seraient différentes. Je ne '

Net-net: veuillez utiliser celui des deux que vous voulez car rien de tout cela n'a d'importance sous aucune forme pratique. (Honnêtement, il existe des facteurs bien plus importants qui affectent les performances de SQL au-delà de cette rubrique).

J'espère que ça aide. J'ai écrit un chapitre de livre sur le fonctionnement de l'optimiseur, mais je ne sais pas s'il est approprié de le poster ici (car j'en tire encore de minuscules redevances, je crois). Donc, au lieu de publier ce que je vais publier un lien vers une conférence que j'ai donnée à SQLBits au Royaume-Uni sur le fonctionnement de l'optimiseur à un niveau élevé afin que vous puissiez voir les différentes phases principales de la recherche un peu plus en détail si vous le souhaitez pour en savoir plus. Voici le lien vidéo: https://sqlbits.com/Sessions/Event6/inside_the_sql_server_query_optimizer

Conor Cunningham MSFT
la source
2
ma conviction est que 1subit également la même expansion. Je base cela sur les tests de performance ici stackoverflow.com/questions/1597442/… voir également l'exemple dans cette réponse d'une requête utilisant l' 1échec de manière inattendue lorsque les autorisations au niveau des colonnes sont en jeu
Martin Smith
21

Dans la norme SQL-92, COUNT(*)signifie spécifiquement "la cardinalité de l'expression de table" (pourrait être une table de base, `VIEW, table dérivée, CTE, etc.).

Je suppose que l'idée était COUNT(*)facile à analyser. L'utilisation d'une autre expression nécessite que l'analyseur s'assure qu'il ne référence aucune colonne ( COUNT('a')aest un littéral et COUNT(a)aest une colonne peut donner des résultats différents).

Dans le même ordre d'idées, COUNT(*)peut être facilement sélectionné par un codeur humain familiarisé avec les normes SQL, une compétence utile lorsque vous travaillez avec plusieurs offres SQL d'un fournisseur.

De plus, dans le cas particulier SELECT COUNT(*) FROM MyPersistedTable;, la pensée est que le SGBD est susceptible de détenir des statistiques pour la cardinalité de la table.

Par conséquent, parce que COUNT(1)et COUNT(*)sont sémantiquement équivalents, j'utilise COUNT(*).

un jour
la source
1
Texte SQL-92 lié de ma réponse sur DBA.SE: dba.stackexchange.com/questions/2511/...
GBN
17

COUNT(*)et COUNT(1)sont les mêmes en cas de résultat et de performance.

Nakul Chaudhary
la source
12

Je m'attendrais à ce que l'optimiseur s'assure qu'il n'y a pas de réelle différence en dehors des cas de bord étranges.

Comme pour tout, la seule vraie façon de le savoir est de mesurer vos cas spécifiques.

Cela dit, je l'ai toujours utilisé COUNT(*).

Richard
la source
Selon la réponse acceptée, cela n'est pas vrai pour MS SQL - il n'y a en fait aucune différence entre les deux.
David Manheim
10

Comme cette question revient encore et encore, voici une réponse de plus. J'espère ajouter quelque chose pour les débutants qui s'interrogent sur les "meilleures pratiques" ici.

SELECT COUNT(*) FROM something compte les enregistrements, ce qui est une tâche facile.

SELECT COUNT(1) FROM something récupère un 1 par enregistrement et compte ensuite les 1 qui ne sont pas nuls, ce qui revient essentiellement à compter les enregistrements, mais plus compliqué.

Cela dit: Bon dbms notez que la deuxième instruction entraînera le même décompte que la première instruction et réinterprétez-le en conséquence, afin de ne pas faire de travail inutile. Donc, généralement, les deux instructions aboutiront au même plan d'exécution et prendront le même temps.

Cependant, du point de vue de la lisibilité, vous devez utiliser la première instruction. Vous voulez compter les enregistrements, donc compter les enregistrements, pas les expressions. Utilisez COUNT (expression) uniquement lorsque vous souhaitez compter les occurrences non nulles de quelque chose.

Thorsten Kettner
la source
8

J'ai effectué un test rapide sur SQL Server 2012 sur une boîte hyper-v de 8 Go de RAM. Vous pouvez voir les résultats par vous-même. Je n'exécutais aucune autre application fenêtrée en dehors de SQL Server Management Studio lors de l'exécution de ces tests.

Mon schéma de table:

CREATE TABLE [dbo].[employee](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_employee] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Nombre total d'enregistrements dans le Employeetableau: 178090131 (~ 178 millions de lignes)

Première requête:

Set Statistics Time On
Go    
Select Count(*) From Employee
Go    
Set Statistics Time Off
Go

Résultat de la première requête:

 SQL Server parse and compile time: 
 CPU time = 0 ms, elapsed time = 35 ms.

 (1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 10766 ms,  elapsed time = 70265 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Deuxième requête:

    Set Statistics Time On
    Go    
    Select Count(1) From Employee
    Go    
    Set Statistics Time Off
    Go

Résultat de la deuxième requête:

 SQL Server parse and compile time: 
   CPU time = 14 ms, elapsed time = 14 ms.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 11031 ms,  elapsed time = 70182 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Vous pouvez remarquer qu'il existe une différence de 83 (= 70265 - 70182) millisecondes qui peut facilement être attribuée à l'état exact du système au moment de l'exécution des requêtes. J'ai aussi fait un seul run, donc cette différence deviendra plus précise si je fais plusieurs runs et que je fais une moyenne. Si pour un ensemble de données aussi énorme, la différence est inférieure à 100 millisecondes, nous pouvons facilement conclure que les deux requêtes n'ont aucune différence de performances présentée par le moteur SQL Server.

Remarque : la RAM atteint près de 100% d'utilisation dans les deux exécutions. J'ai redémarré le service SQL Server avant de démarrer les deux exécutions.

RBT
la source
7
SET STATISTICS TIME ON

select count(1) from MyTable (nolock) -- table containing 1 million records. 

Temps d'exécution SQL Server:
temps CPU = 31 ms, temps écoulé = 36 ms.

select count(*) from MyTable (nolock) -- table containing 1 million records. 

Temps d'exécution SQL Server:
temps CPU = 46 ms, temps écoulé = 37 ms.

J'ai couru des centaines de fois, en effaçant le cache à chaque fois. Les résultats varient de temps en temps en fonction de la charge du serveur, mais ont presque toujours count(*)un temps processeur plus élevé.

Eyal Z.
la source
14
Je ne peux pas reproduire cela. count(*)et count(1)retourner des résultats à quelques ms les uns des autres, même lors du comptage d'une table avec 4,5 millions de lignes, dans mon instance SQL 2008.
Jeff Atwood
2
Parfois, dans certains systèmes, l'instruction exécutée en premier s'exécute toujours plus vite ... avez-vous randomisé l'ordre dans lequel elles sont exécutées?
JosephDoggie
@JosephDoggie, il faut toujours redémarrer le service SQL Server avant d'exécuter chaque requête tout en prenant de telles mesures / statistiques. Lorsque vous venez de démarrer le service SQL Server, chaque exécution devient totalement indépendante et l'ordre de requête n'a pas d'importance. D'un autre côté, si vous ne redémarrez pas le service SQL Server et que le moteur effectue une sorte de mise en cache des plans d'exécution, la requête exécutée plus tard doit s'exécuter plus rapidement, pas la première.
RBT
Les temps d'exécution doivent examiner les plans de requête exacts lors des comparaisons. S'ils sont différents (disons, agrégat de hachage vs agrégat de tri + flux), les résultats ne sont pas comparables. Donc, je vous exhorte à tirer des conclusions ici sans plus de données.
Conor Cunningham MSFT
3

Il y a un article montrant que COUNT(1)sur Oracle n'est qu'un alias COUNT(*), avec une preuve à ce sujet.

Je citerai quelques parties:

Il y a une partie du logiciel de base de données qui s'appelle "L'Optimiseur", qui est défini dans la documentation officielle comme "Logiciel de base de données intégré qui détermine la manière la plus efficace d'exécuter une instruction SQL".

L'un des composants de l'optimiseur est appelé «le transformateur», dont le rôle est de déterminer s'il est avantageux de réécrire l'instruction SQL d'origine en une instruction SQL sémantiquement équivalente qui pourrait être plus efficace.

Souhaitez-vous voir ce que fait l'optimiseur lorsque vous écrivez une requête à l'aide de COUNT (1)?

Avec un utilisateur avec ALTER SESSIONprivilège, vous pouvez mettre un tracefile_identifier, activez le traçage optimiseur et exécutez la COUNT(1)sélection, comme: SELECT /* test-1 */ COUNT(1) FROM employees;.

Après cela, vous devez localiser les fichiers de trace, ce qui peut être fait avec SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';. Plus loin dans le dossier, vous trouverez:

SELECT COUNT(*) COUNT(1)” FROM COURSE”.”EMPLOYEES EMPLOYEES

Comme vous pouvez le voir, c'est juste un alias pour COUNT(*).

Autre commentaire important: le COUNT(*)était vraiment plus rapide il y a deux décennies sur Oracle, avant Oracle 7.3:

Le compte (1) a été réécrit dans le compte (*) depuis la version 7.3 car Oracle aime régler automatiquement les déclarations mythiques. Dans Oracle7 antérieur, oracle devait évaluer (1) pour chaque ligne, en tant que fonction, avant que DETERMINISTIC et NON-DETERMINISTIC existent.

Il y a donc deux décennies, le décompte (*) était plus rapide

Pour une autre base de données comme Sql Server, elle doit être recherchée individuellement pour chacune.

Je sais que cette question est spécifique à Sql Server, mais les autres questions sur SO sur le même sujet, sans mentionner la base de données, ont été fermées et marquées comme dupliquées de cette réponse.

Dherik
la source
1

Dans tous les SGBDR, les deux méthodes de comptage sont équivalentes en termes de résultat qu'elles produisent. En ce qui concerne les performances, je n'ai observé aucune différence de performances dans SQL Server, mais il peut être utile de souligner que certains SGBDR, par exemple PostgreSQL 11, ont des implémentations moins optimales COUNT(1)car ils vérifient la nullité de l'expression d'argument comme on peut le voir dans ce billet .

J'ai trouvé une différence de performances de 10% pour 1 million de lignes lors de l'exécution:

-- Faster
SELECT COUNT(*) FROM t;

-- 10% slower
SELECT COUNT(1) FROM t;
Lukas Eder
la source
0

COUNT (1) n'est pas très différent de COUNT (*), voire pas du tout. Quant à la question de COUNTing NULLable COLUMNs, cela peut être simple pour démontrer les différences entre COUNT (*) et COUNT (<certains col>) -

USE tempdb;
GO

IF OBJECT_ID( N'dbo.Blitzen', N'U') IS NOT NULL DROP TABLE dbo.Blitzen;
GO

CREATE TABLE dbo.Blitzen (ID INT NULL, Somelala CHAR(1) NULL);

INSERT dbo.Blitzen SELECT 1, 'A';
INSERT dbo.Blitzen SELECT NULL, NULL;
INSERT dbo.Blitzen SELECT NULL, 'A';
INSERT dbo.Blitzen SELECT 1, NULL;

SELECT COUNT(*), COUNT(1), COUNT(ID), COUNT(Somelala) FROM dbo.Blitzen;
GO

DROP TABLE dbo.Blitzen;
GO
Graeme
la source