Почему серверу sql необходимо преобразовать результат count (*) в int, прежде чем сравнивать его с переменной int?

11

У меня есть много запросов в моем приложении, где в предложении Имеется сравнение статистической функции count с переменной int. В планах запросов я могу увидеть implicit_convert перед сравнением. Я хочу знать, почему это происходит, потому что согласно документации сервера sql, тип возвращаемого значения функции подсчета int. Итак, почему должно быть неявное преобразование для сравнения двух значений int?

Ниже приводится часть одного такого плана запроса, в котором @IdCount определяется как переменная int.

| --Filter (ГДЕ: ([Expr1022] = [@ IdCount]))    
 | - вычислить скаляр (DEFINE: ([Expr1022] = CONVERT_IMPLICIT (int, [Expr1028], 0))) 
  | - Агрегат потока (GROUP BY: ([MOCK_DB]. [Dbo]. [Scope]. [ScopeID]) DEFINE: ([Expr1028] = Count (*)))
souser
источник

Ответы:

17

Тот факт, что вы сравниваете его с integerпеременной, не имеет значения.

План COUNTвсегда имеет , CONVERT_IMPLICIT(int,[ExprNNNN],0))где ExprNNNNэто метка для экспрессии представляющего результат COUNT.

Я всегда предполагал, что код COUNTпросто вызывает тот же код, что COUNT_BIGи преобразование необходимо для преобразования bigintрезультата обратно int.

На самом деле, COUNT_BIG(*)даже не отличается в плане запроса от COUNT(*). Оба появляются как Scalar Operator(Count(*)).

COUNT_BIG(nullable_column)действительно отличается от плана выполнения, COUNT(nullable_column) но последний все равно получает неявное приведение к int.

Некоторые доказательства того, что это так, приведены ниже.

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)

Это займет около 7 минут для запуска на моем рабочем столе и возвращает следующее

Сообщение 8115, уровень 16, состояние 2, строка 1
Ошибка арифметического переполнения при преобразовании выражения в тип данных int.
Предупреждение: нулевое значение устраняется с помощью агрегата или другой операции SET.

Это указывает на то, что COUNTдолжен был продолжаться после intпереполнения (в 2147483647) и последняя строка (2150000000) был обработан COUNTоператором, что привело к сообщению о NULLвозвращении.

Для сравнения заменим COUNTвыражение на SUM(CASE WHEN N < 2150000000 THEN 1 END)return

Сообщение 8115, уровень 16, состояние 2, строка 1
Ошибка арифметического переполнения при преобразовании выражения в тип данных int.

без ANSIпредупреждения о NULL. Из чего я делаю вывод, что переполнение произошло в этом случае во время самой агрегации до достижения строки 2 150 000 000.

Мартин Смит
источник
@PaulWhite - Спасибо. Я должен был посмотреть на XML. Я смотрел на ScalarOperatorзначение, показанное в окне свойств SSMS.
Мартин Смит