Outer Apply Returning columns unexpectedly NOT NULL when no match

后端 未结 2 1666
感动是毒
感动是毒 2021-01-11 09:52

I\'m hitting some weird behavior on a table valued function when used with OUTER APPLY. I have a simple inline function that returns some simple calculations based on a row

2条回答
  •  爱一瞬间的悲伤
    2021-01-11 10:19

    This is certainly a bug in the product.

    A similar bug was already reported and closed as "Won't Fix".

    Including this question, the linked connect item and another two questions on this site I have seen four cases of this type of behaviour with inline TVFs and OUTER APPLY - All of them were of the format

    OUTER APPLY dbo.SomeFunction(...) F
    

    And returned correct results when written as

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

    So this looks like a possible workaround.

    For the query

    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;
    

    The execution plan looks like

    And the list of output columns in the final projection is. Expr1000, Expr1001, Expr1003, Expr1004.

    However only two of those columns are defined in the table of constants in the bottom right.

    The literal $350000 is defined in the table of constants in the top right (Expr1001). This then gets outer joined onto the table of constants in the bottom right. As no rows match the join condition the two columns defined there (Expr1003, Expr1004) are correctly evaluated as NULL. then finally the compute scalar adds the literal 12 into the data flow as a new column (Expr1000) irrespective of the result of the outer join.

    These are not at all the correct semantics. Compare with the (correct) plan when the inline TVF is manually inlined.

    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 
    

    Here the columns used in the final projection are Expr1003, Expr1004, Expr1005, Expr1006. All of these are defined in the bottom right constant scan.

    In the case of the TVF it all seems to go wrong very early on.

    Adding OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8606); shows the input tree to the process is already incorrect. Expressed in SQL it is something like.

    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 
    

    The full output of that trace flag is as follows (And 8605 shows basically the same tree.)

    *** 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 
    
    *******************
    

提交回复
热议问题