2015-09-04 5 views
14

OUTER APPLYとともに使用すると、テーブル値関数で奇妙な動作が発生します。私は別のテーブルの行に基づいていくつかの簡単な計算を返す単純なインライン関数を持っています。 TVFの入力値がハードコードされたスカラーの場合、返される行はありません。私が同じスカラーを取り、CTEでそれらの中から1行を作成すると、CROSS APPLYを使用して列としてフィードします。結果セットはありません。 OUTER APPLYで同じことをすると、(期待通りに)1行が得られますが、出力列のうちの2つがNULLで、残りの2つがNOT NULLです。 BOLに基づいて、それはOUTER APPLYで起こるべきではありません。これはユーザーエラーですか?私は問題を示すために簡単なバージョンを書いた。外部適用予期せず返す列が一致しないときにNULLを返しません。

--Test set-up 
CREATE FUNCTION dbo.TVFTest 
(
     @keyID INT, 
     @matchValue1 MONEY, 
     @matchValue2 MONEY 
) 
RETURNS TABLE AS RETURN 
(
WITH TestRow 
    AS (SELECT @keyID  AS KeyID, 
       @matchValue1 AS MatchValue1, 
       @matchValue2 AS MatchValue2) 
SELECT KeyID, 
     MatchValue1, 
     MatchValue2, 
     CASE 
     WHEN MatchValue1 <> MatchValue2 
      THEN 'Not equal' 
     ELSE 'Something else' 
     END AS MatchTest 
FROM TestRow 
WHERE MatchValue1 <> MatchValue2 
) 
GO 

クエリ

WITH Test AS 
(
     SELECT 12 AS PropertyID, 
       $350000 AS Ap1, 
       350000 AS Ap2 
) 
SELECT LP.* 
FROM Test T 
OUTER APPLY dbo.TVFTest 
(
     T.PropertyID, 
     T.Ap1, 
     T.Ap2 
) LP; 

結果、予想通りCross Apply返さない行を使用して

+-------+-------------+-------------+-----------+ 
| KeyID | MatchValue1 | MatchValue2 | MatchTest | 
+-------+-------------+-------------+-----------+ 
| 12 | 350000.00 | NULL  | NULL  | 
+-------+-------------+-------------+-----------+ 

。また、CTEを削除してインライン定数を使用すると、行が返されません。

--Scalars, no row here... 
SELECT LP.* 
FROM dbo.TVFTest 
(
     12, 
     $350000, 
     350000 
) LP; 
+0

私はあなたのTVFでフィルタ '' MatchValue1を持っているので、それがあると信じて! = MatchValue2''であり、それぞれのテストは一致する値を提供します。それで、それは一致するものを返しません。その状態を取り除くと、期待した結果が得られると思います。 ... nvrの心は、あなたの質問を誤っているかもしれません... – cocogorilla

+0

OUTER APPLYは、関数に対する外部結合と同様に動作しなければなりません。この例では0の結果が返されました。しかし、TVFによって返されるすべての列はNULLでなければなりません。最初のものはそうではありません。私の質問は - なぜですか?私のコードにバグはありますか? – CDC

+0

あなたは絶対に正しいです...それはWEIRDです...あなたは実際にそれを5列をダンプすることができます...何らかの理由で、マッチドイドがTVPエイリアスの下に来ている...そしてなぜそれがすべきかわからない。 – cocogorilla

答えて

14

これは確かに製品のバグです。

類似のバグwas already reported and closed as "Won't Fix"。この質問、リンク接続項目と私はインラインTVFSとOUTER APPLYと行動のこのタイプの4例を見てきました。このサイトにanothertwo質問を含め

- それらのすべてがフォーマットであった

OUTER APPLY dbo.SomeFunction(...) F 

そして、

OUTER APPLY (SELECT * FROM dbo.SomeFunction(...)) F 

これは考えられる回避策のようです。

クエリ

WITH Test AS 
(
     SELECT 12 AS PropertyID, 
       $350000 AS Ap1, 
       350000 AS Ap2 
) 
SELECT LP.* 
FROM Test T 
OUTER APPLY dbo.TVFTest 
(
     T.PropertyID, 
     T.Ap1, 
     T.Ap2 
) LP; 

の実行計画は、最終的な投影で

enter image description here

と出力列のリストのように見えています。 Expr1000、Expr1001、Expr1003、Expr1004。

ただし、これらの列のうち2つだけが右下の定数の表に定義されています。

リテラル$350000は、右上の定​​数表(Expr1001)で定義されています。これにより、右下の定数の表に外部が結合されます。結合条件に一致する行がないので、そこに定義されている2つの列(Expr1003、Expr1004)はNULLとして正しく評価されます。最後に計算スカラーは、外部結合の結果に関係なく、リテラル12を新しい列(Expr1000)としてデータフローに追加します。

これはまったく正しいセマンティクスではありません。インラインTVFを手動でインライン化するときの(正しい)計画と比較してください。ここ

WITH Test 
    AS (SELECT 12  AS PropertyID, 
       $350000 AS Ap1, 
       350000 AS Ap2) 
SELECT LP.* 
FROM Test T 
     OUTER APPLY (SELECT KeyID, 
          MatchValue1, 
          MatchValue2, 
          CASE 
          WHEN MatchValue1 <> MatchValue2 
           THEN 'Not equal' 
          ELSE 'Something else' 
          END AS MatchTest 
        FROM (SELECT T.PropertyID AS KeyID, 
            T.Ap1  AS MatchValue1, 
            T.Ap2  AS MatchValue2) TestRow 
        WHERE MatchValue1 <> MatchValue2) LP 

enter image description here

最終投影に使用されるカラムはExpr1003, Expr1004, Expr1005, Expr1006です。これらはすべて右下の一定のスキャンで定義されています。

TVFの場合、非常に早く間違っているようです。

OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8606);を追加すると、プロセスへの入力ツリーが既に正しく表示されていません。 SQLで表現されるようなものです。

次のようにそのトレースフラグの完全な出力がある
SELECT Expr1000, 
     Expr1001, 
     Expr1003, 
     Expr1004 
FROM (VALUES (12, 
       $350000, 
       350000)) V1(Expr1000, Expr1001, Expr1002) 
     OUTER APPLY (SELECT Expr1003, 
          IIF(Expr1001 <> Expr1003, 
           'Not equal', 
           'Something else') AS Expr1004 
        FROM (SELECT CAST(Expr1002 AS MONEY) AS Expr1003) D 
        WHERE Expr1001 <> Expr1003) OA 

(そして、8605は基本的に同じツリーを示しています。)

*** Input Tree: *** 
     LogOp_Project COL: Expr1000 COL: Expr1001 COL: Expr1003 COL: Expr1004 

      LogOp_Apply (x_jtLeftOuter) 

       LogOp_Project 

        LogOp_ConstTableGet (1) [empty] 

        AncOp_PrjList 

         AncOp_PrjEl COL: Expr1000 

          ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=12) 

         AncOp_PrjEl COL: Expr1001 

          ScaOp_Const TI(money,ML=8) XVAR(money,Not Owned,Value=(10000units)=(-794967296)) 

         AncOp_PrjEl COL: Expr1002 

          ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=350000) 

       LogOp_Project 

        LogOp_Select 

         LogOp_Project 

          LogOp_ConstTableGet (1) [empty] 

          AncOp_PrjList 

           AncOp_PrjEl COL: Expr1003 

            ScaOp_Convert money,Null,ML=8 

             ScaOp_Identifier COL: Expr1002 

         ScaOp_Comp x_cmpNe 

          ScaOp_Identifier COL: Expr1001 

          ScaOp_Identifier COL: Expr1003 

        AncOp_PrjList 

         AncOp_PrjEl COL: Expr1004 

          ScaOp_IIF varchar collate 53256,Var,Trim,ML=14 

           ScaOp_Comp x_cmpNe 

            ScaOp_Identifier COL: Expr1001 

            ScaOp_Identifier COL: Expr1003 

           ScaOp_Const TI(varchar collate 53256,Var,Trim,ML=9) XVAR(varchar,Owned,Value=Len,Data = (9,Not equal)) 

           ScaOp_Const TI(varchar collate 53256,Var,Trim,ML=14) XVAR(varchar,Owned,Value=Len,Data = (14,Something else)) 

      AncOp_PrjList 

******************* 
+0

Martinに感謝します。私はこれに関する非常に徹底的な研究に感謝します。私は回避策を見つけるだろうと思います。これには「修正されません」とマークされた接続項目があるからです。その間、私はこれに接続項目を開きます。ありがとうございました! – CDC

+0

@CDC - 興味深い回答があれば更新してください。 –

2

さらに調査しました(これは本当に奇妙です)!

これを簡略化することができます。それは暗黙の型変換と関係があるように思えます。それは私がデータ型の周りにしようとした理由だ...

はこれを試してみてください:

--Test set-up 
CREATE FUNCTION dbo.TVFTest 
(
     @ValueInt INT, 
     @ValueMoney MONEY, 
     @ValueVarchar VARCHAR(10), 
     @ValueDate DATE, 
     @DateAsVarchar DATE 

) 
RETURNS TABLE AS RETURN 
(
     SELECT @ValueInt AS ValueInt 
      ,@ValueMoney AS ValueMoney 
      ,@ValueVarchar AS ValueVarchar 
      ,@ValueDate AS ValueDate 
      ,@DateAsVarchar AS DateAsVarchar 
     WHERE 1 != 1 
) 
GO 

この機能が原因WHEREに行を返すことはありません...

DECLARE @d AS DATE='20150101'; 

この型付き日付変数が後で必要になる場合は、GETDATE()による呼び出しで置き換えてください。

--direct call: comes back with no row 
SELECT * FROM dbo.TVFTest(1,2,'test',@d,'20150101'); 

--parameters via CTE: 
WITH Test AS 
(
     SELECT 1 AS valint, 
       2 AS valmoney, 
       'test' AS valchar, 
       @d AS valdate, --try GETDATE() here! 
       '20150101' AS valdateasvarchar 
) 
SELECT * 
FROM Test AS T 
OUTER APPLY dbo.TVFTest(T.valint,T.valmoney,T.valchar,T.valdate,T.valdateasvarchar) AS LP; 

暗黙的にc反転されたパラメータ(MoneyとDateAsVarchar)は表示されませんが、INT、VARCHAR、および "本当の" DATEは実行されます。
実行計画を見てください。 enter image description here この呼び出しはGETDATE()で行われました。それ以外の場合、スカラー演算子は2つしかありません...

EDIT:実行計画の最初の「Compute Scalar」はすべての列を表示します。定数スキャン(定数付き内部テーブルのスキャン)は2つの列しかありませんGETDATE()を使用します)。 「悪い」の列も

--parameters via CTE with single calls 
WITH Test AS 
(
     SELECT 1 AS valint, 
       2 AS valmoney, 
       'test' AS valchar, 
       @d AS valdate, 
       '20150101' AS valdateasvarchar 
) 
SELECT * FROM dbo.TVFTest((SELECT valint FROM Test) 
         ,(SELECT valmoney FROM Test) 
         ,(SELECT valchar FROM Test) 
         ,(SELECT valdate FROM Test) 
         ,(SELECT valdateasvarchar FROM Test)); 
GO 
DROP FUNCTION dbo.TVFTest; 

ただ、もう一つの試み...この段階でCTEの一部であるように思えません、これは期待される結果(空)

でマイconclusioを返します。余分な処理を必要とするスカラー値のみが処理されるため、表示しないでください。余分な作業をせずに通過できるすべてのスカラー値は、関数内で処理されずに表示されます。これはバグです。

あなたの意見は?

+0

ありがとうございます。私はすべての掘削に感謝します。マーティンは頭で釘を打ちましたが、既にこのために提出されたバグを指摘しました。 – CDC

関連する問題