Perché i server collegati hanno una limitazione di 10 rami in un'espressione CASE?


19

Perché questa CASEespressione:

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

Produrre questo risultato?

Messaggio di errore: messaggio 8180, livello 16, stato 1, riga 1
non è stato preparato.
Messaggio 125, Livello 15, Stato 4, riga 1 Le
espressioni dei casi possono essere annidate solo al livello 10.

Chiaramente non c'è CASEun'espressione nidificata qui, sebbene ci siano più di 10 "rami".

Un'altra stranezza. Questa funzione con valori di tabella incorporata produce lo stesso errore:

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
)

Ma un TVF multi-statement simile funziona bene:

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

Risposte:


24

Chiaramente qui non c'è CASEun'espressione nidificata .

Non nel testo della query, no. Ma il parser espande sempre le CASEespressioni nella forma nidificata:

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

Piano di query locale

Quella query è locale (nessun server collegato) e lo scalare di calcolo definisce la seguente espressione:

Espressione CASE nidificata

Questo va bene quando eseguito localmente, perché il parser non vede CASEun'istruzione nidificata su 10 livelli di profondità (anche se ne passa uno alle fasi successive della compilazione di query locale).

Tuttavia, con un server collegato, il testo generato può essere inviato al server remoto per la compilazione. In tal caso, il parser remoto visualizzaCASE un'istruzione nidificata a più di 10 livelli di profondità e viene visualizzato l'errore 8180.

Un'altra stranezza. Questa funzione con valori di tabella incorporata produce lo stesso errore

La funzione in linea viene espansa sul posto nel testo della query originale, quindi non sorprende che gli stessi risultati di errore con il server collegato.

Ma un simile TVF multi-statement funziona bene

Simile, ma non lo stesso. MsTVF comporta una conversione implicita in varchar(max), che accade per impedire che l' CASEespressione venga inviata al server remoto. Poiché CASEviene valutato localmente, un parser non vede mai un over-nidificato CASEe non si verificano errori. Se si modifica la definizione della tabella dal varchar(max)tipo implicito del CASErisultato - varchar(2)- l'espressione viene remotata con msTVF e si otterrà un errore.

Alla fine, l'errore si verifica quando un CASEserver annidato viene valutato dal server remoto. Se CASEnon viene valutato nell'iteratore di query remota, non verrà generato alcun errore. Ad esempio, quanto segue include un CONVERTelemento non remotato, quindi non si verifica alcun errore anche se viene utilizzato un server collegato:

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

CASO non remotato


6

Il mio sospetto è che la query venga riscritta da qualche parte lungo la strada per avere una CASEstruttura leggermente diversa , ad es

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

Credo che questo sia un bug in qualsiasi provider di server collegato che stai utilizzando (in realtà forse tutti loro - l'ho visto segnalato contro diversi). Credo anche che non dovresti trattenere il respiro in attesa di una correzione, né nella funzionalità o nel messaggio di errore confuso che spiega il comportamento - questo è stato segnalato da molto tempo, coinvolge server collegati (che non hanno avuto molto amore da SQL Server 2000) e ha un impatto molto minore su questo confuso messaggio di errore , che deve ancora essere risolto dopo la stessa longevità.

Come sottolinea Paul , SQL Server sta espandendo la tua CASEespressione nella varietà nidificata e al server collegato non piace. Il messaggio di errore è confuso, ma solo perché la conversione sottostante dell'espressione non è immediatamente visibile (né intuitiva in alcun modo).

Una soluzione alternativa (diversa dalla modifica della funzione aggiunta alla domanda) sarebbe quella di creare una vista o una procedura memorizzata sul server collegato e fare riferimento a tale, anziché passare l'intera query tramite il provider del server collegato.

Un altro (supponendo che la tua query sia davvero così semplicistico e vuoi solo il coefficiente numerico di lettere az) è avere:

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

Se hai assolutamente bisogno che funzioni così com'è, ti suggerisco di contattare direttamente l'assistenza e aprire un caso, anche se non posso garantire i risultati: potrebbero semplicemente fornirti soluzioni alternative a cui hai già accesso in questa pagina.


5

puoi aggirare questo

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

Un'altra soluzione alternativa per questo problema consiste nell'utilizzare la logica basata su set, sostituendo l' CASEespressione con un join sinistro (o applicazione esterna) in una tabella di riferimento ( refnel codice seguente), che può essere permanente, temporanea o una tabella / CTE derivata. Se questo è necessario in più query e procedure, preferirei avere questo come tabella 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 ;

-4

un modo per aggirare il problema è includere il test nella whenclausola ie

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

In realtà no. Entrambi 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);traducono esattamente nello stesso piano di esecuzione (sentiti libero di verificarlo tu stesso), dove l'espressione CASE viene ridefinita come CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END- con annidamento, come puoi vedere.
Andriy M,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.