web-dev-qa-db-ja.com

リンクサーバーでは、CASE式のブランチ数が10に制限されているのはなぜですか?

この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
19
Andrey

明らかにここにネストされた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
_

Local query plan

そのクエリはローカル(リンクサーバーなし)であり、Compute Scalarは次の式を定義します。

Nested CASE expression

parserは10レベルを超えるネストされたCASEステートメントを認識しないため、これはローカルで実行すると問題ありません(ただし、ローカルクエリのコンパイルの後のステージに1を渡します)。

ただし、リンクサーバーでは、生成されたテキストmayがコンパイルのためにリモートサーバーに送信されます。その場合、リモートパーサーは、ネストされた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
_

CASE not remoted

24
Paul White 9

私の直感は、クエリが途中でどこかに書き直されて、少し異なるCASE構造を持つようになることです。

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

これは、使用しているリンクサーバープロバイダーのバグだと思います(実際にはおそらくすべてのプロバイダーです。複数に対して報告されているのを確認しました)。私はまた、機能または動作を説明する紛らわしいエラーメッセージのどちらかで修正を待つのを止めるべきではないと思います-これは長い間報告されており、リンクされたサーバーを含みます(SQL以来あまり愛されていませんでした)サーバー2000)に影響し、影響を受けるのは この紛らわしいエラーメッセージ よりもはるかに少ないです。

Paulが指摘 のように、SQL ServerはCASE式をネストされた種類に拡張しており、リンクサーバーはそれを好みません。エラーメッセージは混乱を招きますが、それは、式の基になっている変換がすぐには表示されないためです(いずれにしても直感的ではありません)。

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

もう1つ(クエリが本当に単純で、文字a〜zの数値係数が欲しいだけだと仮定した場合)は、次のようになります。

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

これがそのまま機能するために絶対に必要な場合は、サポートに直接連絡してケースを開くことをお勧めします。ただし、結果を保証することはできません。このページですでにアクセスできる回避策が提供されるだけです。

6
Aaron Bertrand

あなたはこれを回避することができます

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
5
Nik

この問題の別の回避策は、セットベースのロジックを使用して、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 ;
2
ypercubeᵀᴹ