Pourquoi le serveur SQL doit-il convertir le résultat du compte (*) en entier avant de le comparer avec une variable int?

11

J'ai beaucoup de requêtes dans mon application où dans la clause having, j'ai une comparaison de la fonction d'agrégat count avec la variable int. Dans les plans de requête, je peux voir un implicit_convert avant la comparaison. Je veux savoir pourquoi cela se produit car selon la documentation du serveur SQL, le type de retour de la fonction de comptage est int. Alors pourquoi devrait-il y avoir une conversion implicite pour la comparaison de deux valeurs int?

Ce qui suit fait partie d'un tel plan de requête où @IdCount est défini comme une variable int.

| --Filtre (OERE: ([Expr1022] = [@ IdCount]))    
 | --Calculer scalaire (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0)))) 
  | --Stream Aggregate (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]) DEFINE: ([Expr1028] = Count (*)))
souser
la source

Réponses:

17

Le fait que vous le compariez à une integervariable n'est pas pertinent.

Le plan pour a COUNTtoujours un CONVERT_IMPLICIT(int,[ExprNNNN],0))ExprNNNNest le libellé de l'expression représentant le résultat de la COUNT.

Mon hypothèse a toujours été que le code pour COUNTfinit par appeler le même code que COUNT_BIGet que le transtypage est nécessaire pour convertir le bigintrésultat de celui-ci en int.

En fait, il COUNT_BIG(*)n'est même pas distingué dans le plan de requête COUNT(*). Les deux apparaissent comme Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)se distingue dans le plan d'exécution de COUNT(nullable_column) mais ce dernier obtient toujours un cast implicite vers le bas int.

Quelques preuves que c'est le cas se trouvent ci-dessous.

WITH 
E1(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)                                       -- 1*10^1 or 10 rows
, E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
, E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
, E8(N) AS (SELECT 1 FROM E4 a, E4 b)   -- 1*10^8 or 100,000,000 rows
, E16(N) AS (SELECT 1 FROM E8 a, E8 b)  -- 1*10^16 or 10,000,000,000,000,000 rows
, T(N) AS (SELECT TOP (2150000000) 
                  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E16)
SELECT COUNT(CASE WHEN N < 2150000000 THEN 1 END)
FROM T 
OPTION (MAXDOP 1)

Cela prend environ 7 minutes pour s'exécuter sur mon bureau et renvoie ce qui suit

Msg 8115, niveau 16, état 2, ligne 1
Erreur de dépassement arithmétique lors de la conversion de l'expression en type de données int.
Avertissement: la valeur nulle est éliminée par un agrégat ou une autre opération SET.

Ce qui indique que le COUNTdoit avoir continué après qu'un intaurait débordé (à 2147483647) et la dernière ligne (2150000000) a été traitée par l' COUNTopérateur conduisant au message de NULLretour.

A titre de comparaison, remplacer l' COUNTexpression par des SUM(CASE WHEN N < 2150000000 THEN 1 END)retours

Msg 8115, niveau 16, état 2, ligne 1
Erreur de dépassement arithmétique lors de la conversion de l'expression en type de données int.

sans ANSIavertissement NULL. D'où je conclus que le débordement s'est produit dans ce cas lors de l'agrégation elle-même avant que la ligne 2 150 000 000 ne soit atteinte.

Martin Smith
la source
@PaulWhite - Merci. J'aurais dû regarder le XML. Je regardais la ScalarOperatorvaleur indiquée dans la fenêtre des propriétés SSMS.
Martin Smith