Dlaczego serwer SQL musi przekonwertować wynik count (*) na int przed porównaniem go ze zmienną int?

11

Mam wiele zapytań w mojej aplikacji, gdzie w klauzuli have mam porównanie funkcji agregującej count ze zmienną int. W planach zapytań widzę niejawną konwersję przed porównaniem. Chcę wiedzieć, dlaczego tak się dzieje, ponieważ zgodnie z dokumentacją serwera SQL, zwracanym typem funkcji count jest int. Dlaczego więc powinna istnieć niejawna konwersja do porównania dwóch wartości całkowitych?

Poniżej znajduje się część jednego z takich planów zapytań, w którym @IdCount jest zdefiniowane jako zmienna int.

| --Filtr (GDZIE: ([Expr1022] = [@ IdCount]))    
 | --Compute Scalar (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0))) 
  | --Stream Aggregate (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]) DEFINICJA: ([Expr1028] = Count (*)))
souser
źródło

Odpowiedzi:

17

Nie ma znaczenia fakt, że porównujesz ją ze integerzmienną.

Plan COUNTzawsze ma CONVERT_IMPLICIT(int,[ExprNNNN],0))gdzie ExprNNNNjest etykieta dla wyrażenia reprezentującego wynik COUNT.

Zawsze zakładałem, że kod COUNTtylko wywołuje ten sam kod co COUNT_BIGi rzutowanie jest konieczne, aby przekonwertować bigintwynik tego z powrotem na int.

W rzeczywistości COUNT_BIG(*)nawet nie rozróżnia się w planie zapytań COUNT(*). Oba pojawiają się jako Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)wyróżnia się w planie wykonania od, COUNT(nullable_column) ale ten drugi wciąż wraca do domyślnego rzutu int.

Niektóre dowody na to, że tak jest, znajdują się poniżej.

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)

Uruchomienie na moim pulpicie zajmuje około 7 minut i zwraca następujące

Msg 8115, poziom 16, stan 2, wiersz 1
Błąd przepełnienia arytmetycznego podczas konwersji wyrażenia na typ danych int.
Ostrzeżenie: Wartość zerowa jest eliminowana przez agregację lub inną operację SET.

Co wskazuje, że COUNTciąg dalszy musi nastąpić po intprzepełnieniu (w 2147483647), a ostatni wiersz (2150000000) został przetworzony przez COUNToperatora, co spowodowało wyświetlenie komunikatu o zwrocie NULL.

Dla porównania, zastępując COUNTwyrażenie SUM(CASE WHEN N < 2150000000 THEN 1 END)zwrotami

Msg 8115, poziom 16, stan 2, wiersz 1
Błąd przepełnienia arytmetycznego podczas konwersji wyrażenia na typ danych int.

bez ANSIostrzeżenia o NULL. Z tego wnioskuję, że przepełnienie nastąpiło w tym przypadku podczas samej agregacji przed osiągnięciem wiersza 2 150 000 000.

Martin Smith
źródło
@PaulWhite - Dzięki. Powinienem był spojrzeć na XML. Patrzyłem na ScalarOperatorwartość pokazaną w oknie właściwości SSMS.
Martin Smith