外部应用在不匹配时意外返回非空的列 [英] Outer Apply Returning columns unexpectedly NOT NULL when no match

查看:33
本文介绍了外部应用在不匹配时意外返回非空的列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

--测试设置创建函数 dbo.TVFTest(@keyID INT,@matchValue1 钱,@matchValue2 钱)返回表作为返回(带测试行AS (SELECT @keyID AS KeyID,@matchValue1 作为 MatchValue1,@matchValue2 作为 MatchValue2)选择密钥 ID,匹配值1,匹配值2,案件当 MatchValue1 <>匹配值2然后'不等于'ELSE '别的东西'作为匹配测试结束从测试行WHERE MatchValue1 <>匹配值2)走

查询

WITH 测试 AS(选择 12 作为 PropertyID,$350000 AS AP1,350000 AS AP2)选择LP.*从测试 T外部应用 dbo.TVFTest(T.PropertyID,T.Ap1,Ap2)LP;

结果

+-------+-------------+-------------+-----------+|密钥 ID |匹配值 1 |匹配值2 |匹配测试 |+-------+--------------+-------------+-----------+|12 |350000.00 |空 |空 |+-------+--------------+-------------+-----------+

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

--标量,这里没有行...选择LP.*从 dbo.TVFTest(12、35 万美元,350000)LP;

解决方案

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

一个类似的错误

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

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

文字 $350000 在右上角的常量表 (Expr1001) 中定义.然后将其外部连接到右下角的常量表.由于没有行匹配连接条件,在那里定义的两列(Expr1003、Expr1004)被正确评估为 NULL.然后最终计算标量将文字 12 作为新列 (Expr1000) 添加到数据流中,而不管外连接的结果如何.

这些根本不是正确的语义.与手动内联内联 TVF 时的(正确)计划进行比较.

WITH 测试AS (SELECT 12 AS PropertyID,$350000 AS AP1,350000 AS AP2)选择LP.*从测试 T外部应用(选择 KeyID,匹配值1,匹配值2,案件当 MatchValue1 <>匹配值2然后'不等于'ELSE '别的东西'作为匹配测试结束FROM (SELECT T.PropertyID AS KeyID,T.Ap1 AS MatchValue1,T.Ap2 AS MatchValue2) TestRowWHERE MatchValue1 <>匹配值2) LP

这里用于最终投影的列是 Expr1003、Expr1004、Expr1005、Expr1006.所有这些都在右下角的常量扫描中定义.

就 TVF 而言,一切似乎都很早就出错了.

添加 OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8606); 显示进程的输入树已经不正确.用 SQL 表示它是类似的东西.

SELECT Expr1000,Expr1001,Expr1003,Expr1004从(值(12,35 万美元,350000)) V1(Expr1000, Expr1001, Expr1002)外部应用(选择 Expr1003,IIF(Expr1001 <> Expr1003,'不等于','别的东西')AS Expr1004FROM (SELECT CAST(Expr1002 AS MONEY) AS Expr1003) D哪里 Expr1001 <>Expr1003) OA

该跟踪标志的完整输出如下(与 8605 显示的树基本相同.)

*** 输入树:***LogOp_Project COL:Expr1000 COL:Expr1001 COL:Expr1003 COL:Expr1004LogOp_Apply (x_jtLeftOuter)日志Op_ProjectLogOp_ConstTableGet (1) [空]AncOp_PrjListAncOp_PrjEl COL:Expr1000ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=12)AncOp_PrjEl COL:Expr1001ScaOp_Const TI(money,ML=8) XVAR(money,Not Owned,Value=(10000units)=(-794967296))AncOp_PrjEl COL:Expr1002ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=350000)日志Op_Project日志操作选择日志Op_ProjectLogOp_ConstTableGet (1) [空]AncOp_PrjListAncOp_PrjEl COL:Expr1003ScaOp_Convert money,Null,ML=8ScaOp_Identifier COL:Expr1002ScaOp_Comp x_cmpNeScaOp_Identifier COL:Expr1001ScaOp_Identifier COL:Expr1003AncOp_PrjListAncOp_PrjEl COL:Expr1004ScaOp_IIF varchar 整理 53256,Var,Trim,ML=14ScaOp_Comp x_cmpNeScaOp_Identifier COL:Expr1001ScaOp_Identifier COL:Expr1003ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML=9) XVAR(varchar,Owned,Value=Len,Data = (9,Not equal))ScaOp_Const TI(varchar collat​​e 53256,Var,Trim,ML=14) XVAR(varchar,Owned,Value=Len,Data = (14,Something else))AncOp_PrjList*******************

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 in another table. When the input values for the TVF are hard-coded scalars, there is no row returned. When I take the same scalars and make a single row out of them in a CTE, then feed them in as columns using CROSS APPLY, no result set. When I do the same with OUTER APPLY, I get 1 row (as expected), but two of the output columns are NULL and the other two NOT NULL. Based on BOL, that shouldn't happen with an OUTER APPLY. Is this a user error? I wrote a simple version to demonstrate the issue.

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

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;

Results

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

Using Cross Apply returns no rows as expected. Also removing the CTE and using inline constants returns no row.

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

解决方案

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 

*******************

这篇关于外部应用在不匹配时意外返回非空的列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆