Por que os servidores vinculados têm uma limitação de 10 ramificações em uma expressão CASE?

19

Por que essa CASEexpressão:

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

Produzir esse resultado?

Mensagem de erro:
Não foi possível preparar a mensagem 8180, nível 16, estado 1, declarações da linha 1 .
Msg 125, nível 15, estado 4, linha 1 As
expressões de caso podem ser aninhadas apenas no nível 10.

Claramente, não há uma CASEexpressão aninhada aqui, embora haja mais de 10 "ramificações".

Outra esquisitice. Esta função com valor de tabela embutido produz o mesmo erro:

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
)

Mas um TVF de múltiplas declarações semelhante funciona bem:

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
Andrey
fonte

Respostas:

24

Claramente, não há uma CASEexpressão aninhada aqui.

Não está no texto da consulta, não. Mas o analisador sempre expande CASEexpressões para o formulário aninhado:

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

Plano de consulta local

Essa consulta é local (sem servidor vinculado) e o Compute Scalar define a seguinte expressão:

Expressão CASE aninhada

Isso é bom quando executado localmente, porque o analisador não vê uma CASEinstrução aninhada com mais de 10 níveis de profundidade (embora ela passe uma para os estágios posteriores da compilação da consulta local).

No entanto, com um servidor vinculado, o texto gerado pode ser enviado ao servidor remoto para compilação. Se for esse o caso, o analisador remoto verá uma CASEinstrução aninhada com mais de 10 níveis de profundidade e você receberá o erro 8180.

Outra esquisitice. Essa função com valor de tabela embutido produz o mesmo erro

A função em linha é expandida no local no texto da consulta original, portanto, não é surpresa que o mesmo erro ocorra com o servidor vinculado.

Mas um TVF de múltiplas declarações semelhante funciona bem

Semelhante, mas não o mesmo. O msTVF envolve uma conversão implícita em varchar(max), o que acontece para impedir que a CASEexpressão seja enviada ao servidor remoto. Como o CASEé avaliado localmente, um analisador nunca vê um excesso de aninhamento CASEe não há erro. Se você alterar a definição da tabela de varchar(max)para o tipo implícito do CASEresultado - varchar(2)- a expressão será remotada com o msTVF e você receberá um erro.

Por fim, o erro ocorre quando um excesso de aninhamento CASEé avaliado pelo servidor remoto. Se o CASEitem não for avaliado no iterador de Consulta Remota, nenhum erro será gerado. Por exemplo, o seguinte inclui um CONVERTque não é remotamente, portanto, nenhum erro ocorre mesmo que um servidor vinculado seja usado:

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

CASE não remoto

Paul White diz que a GoFundMonica
fonte
6

Meu palpite é que a consulta está sendo reescrita em algum lugar ao longo do caminho para ter uma CASEestrutura ligeiramente diferente , por exemplo

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

Acredito que isso seja um bug em qualquer provedor de servidor vinculado que você esteja usando (na verdade, talvez todos eles - eu já vi isso relatado em vários). Também acredito que você não deve prender a respiração esperando por uma correção, seja na funcionalidade ou na mensagem de erro confusa que explica o comportamento - isso foi relatado há muito tempo, envolve servidores vinculados (que não têm muito amor desde o SQL Server 2000) e afeta muito menos pessoas que essa mensagem de erro confusa , que ainda precisa ser corrigida após a mesma longevidade.

Como Paul aponta , o SQL Server está expandindo sua CASEexpressão para a variedade aninhada, e o servidor vinculado não gosta disso. A mensagem de erro é confusa, mas apenas porque a conversão subjacente da expressão não é imediatamente visível (nem intuitiva de forma alguma).

Uma solução alternativa (além da alteração de função que você adicionou à sua pergunta) seria criar uma exibição ou procedimento armazenado no servidor vinculado e referenciar isso, em vez de passar a consulta completa pelo provedor de servidor vinculado.

Outra (supondo que sua consulta seja realmente simplista e você queira apenas o coeficiente numérico das letras az) é ter:

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

Se você absolutamente precisar que isso funcione como está, sugiro que você entre em contato diretamente com o suporte e abra um caso, embora eu não possa garantir os resultados - eles podem fornecer soluções alternativas às quais você já tem acesso nesta página.

Aaron Bertrand
fonte
5

você pode contornar isso

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
fonte
2

Outra solução alternativa para esse problema é usar a lógica baseada em conjunto, substituindo a CASEexpressão por uma junção esquerda (ou aplicação externa) a uma tabela de referência ( refno código abaixo), que pode ser permanente, temporária ou uma tabela / CTE derivada. Se isso for necessário em várias consultas e procedimentos, eu preferiria ter isso como tabela permanente:

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ᵀᴹ
fonte
-4

Uma maneira de contornar isso é incluir o teste na whencláusula ie

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...
user98586
fonte
Na verdade não. Ambos SELECT CASE v.V WHEN 'a' THEN 1 WHEN 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);e SELECT CASE WHEN v.V = 'a' THEN 1 WHEN v.V = 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);traduzir para exatamente o mesmo plano de execução (sinta-se livre para verificar isso por si mesmo), onde a expressão CASE é redefinida como CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END- com nidificação, como você pode ver.
Andriy M