Чому пов'язані сервери мають обмеження на 10 гілок у виразі CASE?


19

Чому цей CASEвираз:

SELECT CASE column 
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        ... c -> i
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END [col] 
FROM LinkedServer.database.dbo.table

Даєте цей результат?

Повідомлення про помилку: Msg 8180, Рівень 16, Стан 1,
Звіт 1 рядків не вдалося підготувати.
Msg 125, Рівень 15, Стан 4, Рядок 1
Виразки можуть бути вкладені лише до рівня 10.

Зрозуміло, що тут немає вкладеного CASEвиразу, хоча існує більше 10 "гілок".

Ще одна дивацтво. Ця вбудована таблична функція створює ту саму помилку:

ALTER FUNCTION [dbo].[fn_MyFunction]
(   
     @var varchar(20)
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT CASE column 
            WHEN 'a' THEN '1' 
            WHEN 'b' THEN '2' 
            ... c -> i
            WHEN 'j' THEN '10' 
            WHEN 'k' THEN '11'  
        END [col] 
    FROM LinkedServer.database.dbo.table
)

Але подібна TV-версія з різними заявами працює чудово:

ALTER FUNCTION [dbo].[fn_MyFunction]
(   
    @var varchar(20)
)
RETURNS @result TABLE 
(
    value varchar(max)
)
AS
BEGIN
    INSERT INTO @result
    SELECT CASE column 
            WHEN 'a' THEN '1' 
            WHEN 'b' THEN '2' 
            ... c -> i
            WHEN 'j' THEN '10' 
            WHEN 'k' THEN '11'  
        END [col] 
    FROM LinkedServer.database.dbo.table

RETURN;
END

Відповіді:


24

Очевидно, що тут немає вкладеного CASEвиразу.

Ні в тексті запиту, ні. Але парсер завжди розширює CASEвирази до вкладеної форми:

SELECT CASE SUBSTRING(p.Name, 1, 1)
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        WHEN 'c' THEN '3' 
        WHEN 'd' THEN '4' 
        WHEN 'e' THEN '5' 
        WHEN 'f' THEN '6' 
        WHEN 'g' THEN '7' 
        WHEN 'h' THEN '8' 
        WHEN 'i' THEN '9' 
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END
FROM AdventureWorks2012.Production.Product AS p

Місцевий план запитів

Цей запит є локальним (не пов'язаний сервер), і Compute Scalar визначає наступне вираження:

Вкладений вираз CASE

Це добре, коли виконується локально, оскільки аналізатор не бачить вкладеного CASEоператора на глибині 10 рівнів (хоча він переходить на пізніші етапи компіляції локальних запитів).

Однак із пов'язаним сервером згенерований текст може бути відправлений на віддалений сервер для компіляції. Якщо це так, віддалений аналізатор бачить вкладений CASEоператор глибше 10 рівнів, і ви отримуєте помилку 8180.

Ще одна дивацтво. Ця вбудована таблична функція створює ту саму помилку

Вбудована функція розширюється на місці в оригінальному тексті запиту, тому не дивно, що результати помилок пов'язані з пов'язаним сервером.

Але подібна телевізійна програма із заявою на багато заяв працює добре

Подібні, але не однакові. MsTVF передбачає неявну конверсію varchar(max), яка відбувається, щоб запобігти CASEнадсиланню виразу на віддалений сервер. Оскільки CASEоцінка оцінюється локально, аналізатор ніколи не бачить перенастроєного CASEі помилки немає. Якщо ви зміните визначення таблиці з varchar(max)на неявний тип CASEрезультату - varchar(2)- вираз буде видалено за допомогою msTVF, і ви отримаєте помилку.

Зрештою, помилка виникає, коли CASEвіддалений сервер оцінюється завищеною вкладеністю . Якщо значення CASEне оцінюється в ітераторі віддалених запитів, результатів помилок немає. Наприклад, нижче наведено таке, CONVERTяке не видаляється, тому помилка не виникає, навіть якщо використовується пов'язаний сервер:

SELECT CASE CONVERT(varchar(max), SUBSTRING(p.Name, 1, 1))
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        WHEN 'c' THEN '3' 
        WHEN 'd' THEN '4' 
        WHEN 'e' THEN '5' 
        WHEN 'f' THEN '6' 
        WHEN 'g' THEN '7' 
        WHEN 'h' THEN '8' 
        WHEN 'i' THEN '9' 
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END
FROM SQL2K8R2.AdventureWorks.Production.Product AS p

Справа не видалена


6

Моя думка полягає в тому, що запит переписується десь по шляху, щоб мати дещо іншу CASEструктуру, наприклад

CASE WHEN column = 'a' THEN '1' ELSE CASE WHEN column = 'b' THEN '2' ELSE ...

Я вважаю, що це помилка в будь-якому постачальнику зв’язаних серверів, який ви використовуєте (насправді, можливо, всі вони - я бачив, як це повідомлялося проти кількох). Я також вважаю, що вам не слід затримувати дихання, чекаючи виправлення, як у функціональності, так і в заплутаному повідомленні про помилку, що пояснює поведінку - про це повідомлялося вже давно, пов'язані пов'язані сервери (які не любили багато часу з часу SQL Server 2000) та впливає на набагато меншу кількість людей, ніж це заплутане повідомлення про помилку , яке ще потрібно виправити після того ж довголіття.

Як зазначає Пол , SQL Server розширює ваше CASEвираження до вкладеної різноманітності, а пов'язаному серверу це не подобається. Повідомлення про помилку є заплутаним, але лише тому, що базове перетворення виразу не відразу видно (ані інтуїтивно зрозумілим).

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

Іншим (якщо припустити, що ваш запит справді такий спрощений, і ви просто хочете, щоб числовий коефіцієнт букв az):

SELECT [col] = RTRIM(ASCII([column])-96)
FROM LinkedServer.database.dbo.table;

Якщо вам абсолютно потрібна ця робота, як є, я пропоную вам зв’язатися безпосередньо зі службою підтримки та відкрити справу, хоча я не можу поручити результати - вони можуть просто забезпечити вам обхідні шляхи, до яких ви вже маєте доступ на цій сторінці.


5

ви можете обійти це за допомогою

SELECT COALESCE(
CASE SUBSTRING(p.Name, 1, 1)
    WHEN 'a' THEN '1' 
    WHEN 'b' THEN '2' 
    WHEN 'c' THEN '3' 
    WHEN 'd' THEN '4' 
    WHEN 'e' THEN '5' 
    WHEN 'f' THEN '6' 
    WHEN 'g' THEN '7' 
    WHEN 'h' THEN '8' 
    WHEN 'i' THEN '9' 
    ELSE NULL
END,
CASE SUBSTRING(p.Name, 1, 1)
    WHEN 'j' THEN '10' 
    WHEN 'k' THEN '11'  
END)
FROM SQL2K8R2.AdventureWorks.Production.Product AS p

2

Ще одне вирішення цієї проблеми полягає у використанні заданої логіки, замінюючи CASEвираз лівим з'єднанням (або зовнішнім застосуванням) до опорної таблиці ( refв коді нижче), яка може бути постійною, тимчасовою або похідною таблицею / CTE. Якщо це потрібно в декількох запитах і процедурах, я вважаю за краще це мати як постійну таблицю:

SELECT ref.result_column AS [col] 
FROM LinkedServer.database.dbo.table AS t
  LEFT JOIN
    ( VALUES ('a',  '1'),
             ('b',  '2'), 
             ('c',  '3'),
             ---
             ('j', '10'),
             ('k', '11')
    ) AS ref (check_col, result_column) 
    ON ref.check_col = t.column ;

-4

Один із способів обійти це - включити тест у whenпункт, тобто

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...

Насправді ні. І те, SELECT CASE v.V WHEN 'a' THEN 1 WHEN 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);і інше, SELECT CASE WHEN v.V = 'a' THEN 1 WHEN v.V = 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);перекладіть точно на той самий план виконання (сміливо переконайтеся, що для себе), де вираз CASE переосмислюється як CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END- з введенням, як ви бачите.
Андрій М
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.