Почему связанные выражения имеют ограничение в 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

Произведите этот результат?

Сообщение об ошибке: Сообщение 8180, уровень 16, состояние 1, строка 1
Не удалось подготовить оператор (ы).
Сообщение 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
)

Но похожий мульти-оператор TVF работает нормально:

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.

Еще одна странность. Эта встроенная табличная функция выдает ту же ошибку

Встроенная функция развернута на месте в исходный текст запроса, поэтому неудивительно, что те же самые ошибки появляются на связанном сервере.

Но аналогичный мульти-оператор TVF работает нормально

Похоже, но не то же самое. 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

ДЕЛО не удалено

Пол Уайт говорит, что GoFundMonica
источник
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
Nik
источник
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 ;
ypercubeᵀᴹ
источник
-4

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

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...
user98586
источник
Вообще-то, нет. И те, 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- со вложением, как вы можете видеть.
Андрей М