¿Por qué el servidor sql necesita convertir el resultado count (*) en int antes de compararlo con una variable int?

11

Tengo muchas consultas en mi aplicación donde, en la cláusula have, tengo una comparación de la función de agregado de conteo con la variable int. En los planes de consulta, puedo ver una conversión implícita antes de la comparación. Quiero saber por qué sucede esto porque según la documentación del servidor sql, el tipo de retorno de la función de conteo es int. Entonces, ¿por qué debería haber una conversión implícita para la comparación de dos valores int?

Lo siguiente es una parte de uno de esos planes de consulta donde @IdCount se define como una variable int.

| --Filter (DONDE: ([Expr1022] = [@ IdCount]))    
 | --Escalar de computadora (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0))) 
  | - Agregado de flujo (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]) DEFINE: ([Expr1028] = Count (*)))
souser
fuente

Respuestas:

17

El hecho de que lo esté comparando con una integervariable es irrelevante.

El plan para COUNTsiempre tiene un CONVERT_IMPLICIT(int,[ExprNNNN],0))dónde ExprNNNNes la etiqueta para la expresión que representa el resultado de COUNT.

Mi suposición siempre ha sido que el código para COUNTsimplemente termina llamando al mismo código COUNT_BIGy el reparto es necesario para convertir el bigintresultado de eso nuevamente int.

De hecho, COUNT_BIG(*)ni siquiera se distingue en el plan de consulta COUNT(*). Ambos aparecen como Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)sí se distingue en el plan de ejecución, COUNT(nullable_column) pero este último todavía tiene un reparto implícito de nuevo int.

Alguna evidencia de que este es el caso está abajo.

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)

Esto demora aproximadamente 7 minutos en ejecutarse en mi escritorio y devuelve lo siguiente

Mensaje 8115, Nivel 16, Estado 2, Línea 1
Error de desbordamiento aritmético al convertir la expresión al tipo de datos int.
Advertencia: el valor nulo es eliminado por un agregado u otra operación SET.

Lo que indica que COUNTdebe haber continuado después de que intse hubiera desbordado un (en 2147483647) y la última fila (2150000000) fue procesada por el COUNToperador que condujo al mensaje de NULLdevolución.

A modo de comparación, reemplazando la COUNTexpresión con SUM(CASE WHEN N < 2150000000 THEN 1 END)retornos

Mensaje 8115, Nivel 16, Estado 2, Línea 1
Error de desbordamiento aritmético al convertir la expresión al tipo de datos int.

sin ANSIprevio aviso NULL. De lo cual concluyo que el desbordamiento ocurrió en este caso durante la agregación misma antes de alcanzar la fila 2,150,000,000.

Martin Smith
fuente
@PaulWhite - Gracias. Debería haber mirado el XML. Estaba mirando el ScalarOperatorvalor que se muestra en la ventana de propiedades de SSMS.
Martin Smith