При додаванні вибору було перевищено рівень вкладеності скалярних функцій самопосилань


24

Призначення

При спробі створити тестовий приклад функції самостійного посилання, одна версія виходить з ладу, а інша успішна.

Єдина відмінність - додавання SELECTдо функції функції, що призводить до різного плану виконання для обох.


Функція, яка працює

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

Виклик функції

SELECT dbo.test5(3);

Повертається

(No column name)
3

Функція, яка не працює

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

Виклик функції

SELECT dbo.test6(3);

або

SELECT dbo.test6(2);

Результати помилки

Максимально збережена процедура, функція, тригер або перегляд рівня вкладеності (межа 32).

Здогадування причини

Існує додатковий скаляр обчислень на оціночному плані невдалої функції

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="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 істота

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

Що може пояснити рекурсивні посилання, що перевищують 32.

Актуальне питання

Доданий SELECTробить виклик функції знову і знову, що призводить до нескінченного циклу, але чому додавання SELECTдає цей результат?


додаткова інформація

Розрахункові плани виконання

БД <> Фіддл

Build version:
14.0.3045.24

Тестовано на сумісності_рівнів 100 і 140

Відповіді:


26

Це помилка в нормалізації проекту , виявлена ​​за допомогою підзапиту всередині виразу регістру з недетермінованою функцією.

Для пояснення нам потрібно відзначити дві речі:

  1. SQL Server не може виконати підзапити безпосередньо, тому вони завжди розгортають або перетворюють на застосувати .
  2. Семантика 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.