外部应用在不匹配时意外返回列NOT NULL

CDC*_*CDC 14 t-sql sql-server sql-server-2008 sql-server-2012

当与OUTER APPLY一起使用时,我在表值函数上遇到了一些奇怪的行为.我有一个简单的内联函数,它返回一些基于另一个表中的行的简单计算.当TVF的输入值是硬编码标量时,不返回任何行.当我在CTE中使用相同的标量并从中生成一行时,然后使用CROSS APPLY将它们作为列提供,没有结果集.当我使用OUTER APPLY执行相同操作时,我得到1行(如预期的那样),但是其中两个是输出列NULL,另外两个是NOT NULL.基于BOL,这不应该发生OUTER APPLY.这是用户错误吗?我写了一个简单的版本来演示这个问题.

--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
Run Code Online (Sandbox Code Playgroud)

询问

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;
Run Code Online (Sandbox Code Playgroud)

结果

+-------+-------------+-------------+-----------+
| KeyID | MatchValue1 | MatchValue2 | MatchTest |
+-------+-------------+-------------+-----------+
|    12 | 350000.00   | NULL        | NULL      |
+-------+-------------+-------------+-----------+
Run Code Online (Sandbox Code Playgroud)

使用Cross Apply返回没有按预期方式行.同时删除CTE并使用内联常量不会返回任何行.

--Scalars, no row here...
SELECT LP.*
FROM dbo.TVFTest
(
       12,
       $350000,
       350000
) LP;
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 14

这肯定是产品中的一个错误.

已报告类似的错误,并将其关闭为"无法修复".

包括这个问题,链接的连接项和本网站上的另外 两个问题我已经看到了四种内联TVF的此类行为案例OUTER APPLY- 所有这些都是格式化的

OUTER APPLY dbo.SomeFunction(...) F
Run Code Online (Sandbox Code Playgroud)

写作时返回正确的结果

OUTER APPLY (SELECT * FROM dbo.SomeFunction(...)) F
Run Code Online (Sandbox Code Playgroud)

所以这看起来像是一种可能的解决方法.

对于查询

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;
Run Code Online (Sandbox Code Playgroud)

执行计划看起来像

在此输入图像描述

并且最终投影中的输出列列表是.Expr1000,Expr1001,Expr1003,Expr1004.

但是,在右下角的常量表中只定义了其中两列.

文字$350000在右上角的常量表(Expr1001)中定义.然后将其外部连接到右下角的常量表中.由于没有行匹配连接条件,因此在那里定义的两列(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 
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

这里是最终投影中使用的列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 
Run Code Online (Sandbox Code Playgroud)

该跟踪标志的完整输出如下(并且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 

*******************
Run Code Online (Sandbox Code Playgroud)