Це помилка в нормалізації проекту , виявлена за допомогою підзапиту всередині виразу регістру з недетермінованою функцією.
Для пояснення нам потрібно відзначити дві речі:
- SQL Server не може виконати підзапити безпосередньо, тому вони завжди розгортають або перетворюють на застосувати .
- Семантика
CASE
такої, що THEN
вираз слід оцінювати лише у тому випадку, якщо WHEN
пропозиція повертає істину.
Таким чином, тривіальний підзапит, введений у проблемному випадку, приводить до оператора застосунку (вкладені вкладені петлі). Для виконання другої вимоги 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 Feedback для SQL Server.