Warum muss der SQL Server das Ergebnis count (*) in int konvertieren, bevor er es mit einer int-Variablen vergleicht?

11

Ich habe viele Abfragen in meiner Anwendung, bei denen ich in der have-Klausel einen Vergleich der Zählaggregatfunktion mit der int-Variablen habe. In den Abfrageplänen kann ich vor dem Vergleich eine implizite Konvertierung sehen. Ich möchte wissen, warum dies geschieht, da gemäß der SQL Server-Dokumentation der Rückgabetyp der Zählfunktion int ist. Warum sollte es also eine implizite Konvertierung zum Vergleich zweier int-Werte geben?

Das Folgende ist ein Teil eines solchen Abfrageplans, in dem @IdCount als int-Variable definiert ist.

| --Filter (WHERE: ([Expr1022] = [@ IdCount]))    
 | --Compute Scalar (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0))) 
  | --Stream Aggregate (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]) DEFINE: ([Expr1028] = Count (*)))
Souser
quelle

Antworten:

17

Die Tatsache, dass Sie es mit einer integerVariablen vergleichen, ist irrelevant.

Der Plan für hat COUNTimmer ein CONVERT_IMPLICIT(int,[ExprNNNN],0))Wo ExprNNNNist die Bezeichnung für den Ausdruck, der das Ergebnis von darstellt COUNT.

Meine Annahme war immer, dass der Code für COUNTnur den gleichen Code aufruft wie COUNT_BIGund die Besetzung notwendig ist, um das bigintErgebnis davon wieder in umzuwandeln int.

Tatsächlich COUNT_BIG(*)wird im Abfrageplan nicht einmal von unterschieden COUNT(*). Beide erscheinen als Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)wird im Ausführungsplan von unterschieden, COUNT(nullable_column) aber letzterer erhält immer noch eine implizite Besetzung zurück zu int.

Einige Beweise dafür, dass dies der Fall ist, sind unten aufgeführt.

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)

Die Ausführung auf meinem Desktop dauert ca. 7 Minuten und gibt Folgendes zurück

Nachricht 8115, Ebene 16, Status 2, Zeile 1
Arithmetischer Überlauffehler beim Konvertieren des Ausdrucks in den Datentyp int.
Warnung: Der Nullwert wird durch eine aggregierte oder andere SET-Operation entfernt.

Dies zeigt an, dass das COUNTMuss fortgesetzt werden muss, nachdem ein intÜberlauf (bei 2147483647) und die letzte Zeile (2150000000) vom COUNTBediener verarbeitet wurde , was zu der Nachricht über NULLdie Rückgabe führte.

Zum Vergleich: Ersetzen des COUNTAusdrucks durch SUM(CASE WHEN N < 2150000000 THEN 1 END)Rückgabe

Nachricht 8115, Ebene 16, Status 2, Zeile 1
Arithmetischer Überlauffehler beim Konvertieren des Ausdrucks in den Datentyp int.

ohne ANSIVorwarnung NULL. Daraus schließe ich, dass der Überlauf in diesem Fall während der Aggregation selbst stattgefunden hat, bevor die Zeile 2.150.000.000 erreicht wurde.

Martin Smith
quelle
@ PaulWhite - Danke. Ich hätte mir das XML ansehen sollen. Ich habe mir den ScalarOperatorWert angesehen, der im SSMS-Eigenschaftenfenster angezeigt wird.
Martin Smith