Por que o sql server precisa converter o resultado da contagem (*) em int antes de compará-lo com uma variável int?

11

Eu tenho muitas consultas no meu aplicativo, onde na cláusula having, tenho comparação da função agregada count com a variável int. Nos planos de consulta, posso ver um implicit_convert antes da comparação. Quero saber por que isso acontece porque, conforme a documentação do servidor sql, o tipo de retorno da função count é int. Então, por que deveria haver uma conversão implícita para comparação de dois valores int?

A seguir, parte de um desses planos de consulta em que @IdCount é definido como uma variável int.

| --Filter (WHERE: ([Expr1022] = [@ IdCount]))    
 | --Computar escalar (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0))) 
  | - Agregado de fluxo (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]]) DEFINE: ([Expr1028] = Count (*)))
souser
fonte

Respostas:

17

O fato de você estar comparando-o com uma integervariável é irrelevante.

O plano para COUNTsempre tem um CONVERT_IMPLICIT(int,[ExprNNNN],0))onde ExprNNNNé o rótulo da expressão que representa o resultado do COUNT.

Minha suposição sempre foi que o código COUNTacaba chamando o mesmo código COUNT_BIGe o elenco é necessário para converter o bigintresultado desse código novamente em int.

De fato, COUNT_BIG(*)nem sequer se distingue no plano de consulta COUNT(*). Ambos aparecem como Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)se diferencia no plano de execução, COUNT(nullable_column) mas o último ainda recebe uma conversão implícita de volta para int.

Algumas evidências de que esse é o caso estão abaixo.

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)

Demora cerca de 7 minutos para ser executado na minha área de trabalho e retorna o seguinte

Msg 8115, nível 16, estado 2, linha 1
Erro de estouro aritmético ao converter expressão em tipo de dados int.
Aviso: O valor nulo é eliminado por uma operação agregada ou outra operação SET.

O que indica que o item COUNTdeve ter continuado após um inttransbordamento (em 2147483647) e a última linha (2150000000) foi processada pelo COUNToperador, levando à mensagem sobre NULLa devolução.

A título de comparação, substituindo a COUNTexpressão por SUM(CASE WHEN N < 2150000000 THEN 1 END)retornos

Msg 8115, nível 16, estado 2, linha 1
Erro de estouro aritmético ao converter expressão em tipo de dados int.

sem ANSIaviso sobre NULL. A partir do qual concluo que o excesso ocorreu neste caso durante a agregação em si antes que a linha 2.150.000.000 fosse alcançada.

Martin Smith
fonte
@PaulWhite - Obrigado. Eu deveria ter olhado para o XML. Eu estava olhando o ScalarOperatorvalor mostrado na janela de propriedades do SSMS.
Martin Smith