Warum haben Verbindungsserver eine Beschränkung auf 10 Zweige in einem CASE-Ausdruck?

19

Warum bedeutet dieser CASEAusdruck:

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

Dieses Ergebnis produzieren?

Fehlermeldung: Meldung 8180, Ebene 16, Status 1, Zeile 1
Anweisung (en) konnte nicht vorbereitet werden.
Meldung 125, Ebene 15, Status 4, Zeile 1
Groß- / Kleinschreibung darf nur bis Ebene 10 verschachtelt werden.

Offensichtlich gibt es hier keinen verschachtelten CASEAusdruck, obwohl es mehr als 10 "Zweige" gibt.

Eine weitere Kuriosität. Diese Inline-Tabellenwertfunktion erzeugt denselben Fehler:

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
)

Aber eine ähnliche TVF mit mehreren Aussagen funktioniert gut:

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
Andrej
quelle

Antworten:

24

Offensichtlich gibt es hier keinen verschachtelten CASEAusdruck.

Nicht im Abfragetext, nein. Der Parser erweitert jedoch CASEAusdrücke immer auf die verschachtelte Form:

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

Lokaler Abfrageplan

Diese Abfrage ist lokal (kein Verbindungsserver) und der Berechnungsskalar definiert den folgenden Ausdruck:

Geschachtelter CASE-Ausdruck

Dies ist in Ordnung, wenn es lokal ausgeführt wird, da der Parser keine verschachtelte CASEAnweisung mit einer Tiefe von mehr als 10 Ebenen sieht (obwohl eine Anweisung an die späteren Phasen der Kompilierung lokaler Abfragen weitergeleitet wird).

Bei einem Verbindungsserver kann der generierte Text jedoch zur Kompilierung an den Remote-Server gesendet werden. In diesem Fall sieht der Remote-Parser eine verschachtelte CASEAnweisung, die mehr als 10 Ebenen tief ist, und es wird der Fehler 8180 angezeigt.

Eine weitere Kuriosität. Diese Inline-Tabellenwertfunktion erzeugt denselben Fehler

Die Inline-Funktion wird direkt in den ursprünglichen Abfragetext integriert, sodass es nicht verwunderlich ist, dass derselbe Fehler beim Verbindungsserver auftritt.

Aber eine ähnliche TVF mit mehreren Aussagen funktioniert gut

Ähnlich, aber nicht gleich. Die msTVF beinhaltet eine implizite Konvertierung nach varchar(max), die verhindert, dass der CASEAusdruck an den Remote-Server gesendet wird. Da die CASElokal ausgewertet wird, sieht ein Parser nie eine übergeordnete CASEund es liegt kein Fehler vor. Wenn Sie die Tabellendefinition von varchar(max)in den impliziten Typ des CASEErgebnisses varchar(2)ändern, wird der Ausdruck mit der msTVF entfernt und Sie erhalten eine Fehlermeldung.

Letztendlich tritt der Fehler auf, wenn ein übermäßig verschachteltes CASEObjekt vom Remoteserver ausgewertet wird. Wenn das CASEim Remote Query Iterator nicht ausgewertet wird, tritt kein Fehler auf. Das folgende Beispiel enthält ein CONVERT, das nicht entfernt ist, sodass kein Fehler auftritt, obwohl ein Verbindungsserver verwendet wird:

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

FALL nicht entfernt

Paul White sagt GoFundMonica
quelle
6

Meine Vermutung ist, dass die Abfrage irgendwo auf dem Weg neu geschrieben wird, um eine etwas andere CASEStruktur zu haben, z

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

Ich glaube, dies ist ein Fehler in dem von Ihnen verwendeten Anbieter von Verbindungsservern (in der Tat vielleicht alle - ich habe gesehen, dass es gegen mehrere gemeldet wurde). Ich glaube auch, dass Sie nicht den Atem anhalten sollten, um auf eine Behebung zu warten, weder in der Funktionalität noch in der verwirrenden Fehlermeldung, die das Verhalten erklärt - dies wurde seit langer Zeit berichtet, betrifft Verbindungsserver (die seit SQL nicht mehr viel Liebe hatten Server 2000) und betrifft weit weniger Personen als diese verwirrende Fehlermeldung , die nach der gleichen Lebensdauer noch behoben werden muss.

Wie Paul betont , erweitert SQL Server Ihren CASEAusdruck auf die verschachtelte Sorte, und der Verbindungsserver mag es nicht. Die Fehlermeldung ist verwirrend, aber nur, weil die zugrunde liegende Konvertierung des Ausdrucks nicht sofort sichtbar ist (noch in irgendeiner Weise intuitiv).

Eine Problemumgehung (mit Ausnahme der Funktionsänderung, die Sie Ihrer Frage hinzugefügt haben) wäre, eine Ansicht oder gespeicherte Prozedur auf dem Verbindungsserver zu erstellen und auf diese zu verweisen, anstatt die vollständige Abfrage über den Verbindungsserver-Anbieter weiterzuleiten.

Eine andere (vorausgesetzt, Ihre Abfrage ist wirklich so simpel und Sie möchten nur den numerischen Koeffizienten der Buchstaben az) lautet:

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

Wenn Sie dies unbedingt benötigen, sollten Sie sich direkt an den Support wenden und einen Fall eröffnen. Ich kann jedoch nicht für die Ergebnisse bürgen. Möglicherweise werden Ihnen hier nur Problemumgehungen bereitgestellt, auf die Sie auf dieser Seite bereits Zugriff haben.

Aaron Bertrand
quelle
5

Sie können dies umgehen, indem Sie

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

Eine andere Problemumgehung für dieses Problem besteht in der Verwendung einer satzbasierten Logik, bei der der CASEAusdruck durch eine linke Verknüpfung (oder äußere Verknüpfung) zu einer Referenztabelle ( refim folgenden Code) ersetzt wird. Diese kann entweder permanent, temporär oder eine abgeleitete Tabelle / CTE sein. Wenn dies in mehreren Abfragen und Prozeduren benötigt wird, würde ich es vorziehen, dies als permanente Tabelle zu haben:

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

Eine Möglichkeit, dies zu umgehen, besteht darin, den Test in die whenKlausel aufzunehmen, d. h

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...
user98586
quelle
Nicht wirklich. Beides SELECT CASE v.V WHEN 'a' THEN 1 WHEN 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);und die SELECT CASE WHEN v.V = 'a' THEN 1 WHEN v.V = 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);Übersetzung erfolgen in genau denselben Ausführungsplan (können Sie sich selbst davon überzeugen), in dem der CASE-Ausdruck wie folgt neu definiert wird: CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END- Wie Sie sehen, mit Verschachtelung.
Andriy M