Это ошибка в нормализации проекта , обнаруженная с помощью подзапроса внутри выражения case с недетерминированной функцией.
Чтобы объяснить, нам нужно отметить две вещи заранее:
- SQL Server не может выполнять подзапросы напрямую, поэтому они всегда развертываются или преобразуются в приложение .
- Семантика
CASE
такова, что THEN
выражение должно оцениваться только в том случае, если WHEN
предложение возвращает true.
Таким образом, (тривиальный) подзапрос, введенный в проблемном случае, приводит к оператору apply (объединение вложенных циклов). Чтобы соответствовать второму требованию, SQL Server изначально помещает выражениеdbo.test6(1) + dbo.test6(2)
на внутренней стороне применения:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
... с CASE
семантикой, учитываемой сквозным предикатом в соединении:
[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)
Внутренняя сторона цикла оценивается, только если условие прохода оценивается как ложное (имеется в виду@i = 3
). Пока это все правильно. Compute Scalar следующие соединения вложенных циклов также чтит CASE
семантику правильно:
[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
Проблема заключается в том, что на этапе нормализации проекта компиляции запроса он Expr1000
не коррелирует и определяет, что это будет безопасно ( рассказчик: это не так). ) вывести его за пределы цикла:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
Это нарушает * семантику, реализованную в сквозном предикате, поэтому функция вычисляется, когда не должно быть, и получается бесконечный цикл.
Вы должны сообщить об этой ошибке. Обходной путь - предотвратить перемещение выражения за пределы приложения, сделав его коррелированным (т. Е. Включив его @i
в выражение), но это, конечно, хак. Есть способ отключить нормализацию проекта, но раньше меня просили не делиться им публично, поэтому я не буду.
Эта проблема не возникает в SQL Server 2019, когда скалярная функция встроена , потому что логика встраивания работает непосредственно в разобранном дереве (задолго до нормализации проекта). Простая логика в вопросе может быть упрощена с помощью встроенной логики к нерекурсивной:
[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))
... который возвращает 3.
Другой способ проиллюстрировать основную проблему:
-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error()
RETURNS integer
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
RETURN 1/0;
END;
GO
DECLARE @i integer = 1;
SELECT
CASE
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
ELSE NULL
END;
Воспроизводит на последних сборках всех версий от 2008 R2 до 2019 CTP 3.0.
Еще один пример (без скалярной функции), предоставленный Мартином Смитом :
SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))
Это имеет все ключевые элементы, необходимые:
CASE
(реализовано как ScaOp_IIF
)
- Недетерминированная функция (
CRYPT_GEN_RANDOM
)
- Подзапрос на ветви, который не должен быть выполнен (
(SELECT ...)
)
* Строго говоря, приведенное выше преобразование все еще может быть правильным, если оценка Expr1000
была отложена правильно, поскольку на него ссылается только безопасная конструкция:
[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
... но для этого требуется внутренний флаг ForceOrder (не подсказка запроса), который также не установлен. В любом случае реализация логики, применяемой при нормализации проекта, является неправильной или неполной.
Отчет об ошибке на сайте обратной связи Azure для SQL Server.