リンクサーバーでCASE式に10ブランチという制限があるのはなぜですか?


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にのみネストできます。

CASE10個以上の「ブランチ」がありますが、ここには明らかにネストされた式はありません。

もう一つの奇妙な。このインラインテーブル値関数は、同じエラーを生成します。

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

ケースはリモート接続されていません


6

私の考えでは、クエリは途中でどこかで書き直されており、わずかに異なるCASE構造を持っています。

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

これは、使用しているリンクサーバープロバイダーのバグだと思います(実際、おそらくすべてのバグです-いくつかのバグを報告しました)。また、機能または動作を説明する混乱するエラーメッセージのいずれかで、修正を待つことはできません-これは長い間報告されていますが、リンクサーバー(SQL以来あまり愛されていません) Server 2000)、およびこの混乱するエラーメッセージよりもはるかに少ない人々に影響します、同じ寿命の後にまだ修正されていません。

パウロが指摘し、SQL Serverは拡大しているCASEネストされた様々な表現を、およびリンクサーバーはそれが好きではありません。エラーメッセージは混乱しますが、それは式の基になる変換がすぐに表示されないためです(何らかの方法で直感的でもありません)。

1つの回避策(質問に追加した関数の変更以外)は、リンクサーバープロバイダーを介して完全なクエリを渡す代わりに、リンクサーバーでビューまたはストアドプロシージャを作成し、それを参照することです。

別の(クエリが本当にこの単純であると仮定し、文字a〜zの数値係数が必要な場合)は、次のようにすることです。

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以下のコードを参照)。複数のクエリとプロシージャでこれが必要な場合は、これを永続的なテーブルとして使用することをお勧めします。

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

これを回避する1つの方法は、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
アンドリーM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.