外部应用当没有匹配时

时间:2015-09-04 22:22:47

标签: sql-server sql-server-2008 tsql 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

查询

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;

结果

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

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

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

2 个答案:

答案 0 :(得分:14)

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

类似的错误was already reported and closed as "Won't Fix"

在此网站上包含此问题,关联的连接项和another two个问题我已经看到了四种内联TVF和OUTER APPLY此类行为的案例 - 所有这些都是格式

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。

但是,这些列中只有两列是在右下角的常量表中定义的。

文字$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 

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 

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

答案 1 :(得分:2)

我做了一些进一步的研究(SQL Server 2012) - 这真的很奇怪!

您可以简化此操作。在我看来,它与隐式类型转换有关。这就是我尝试使用数据类型的原因......

试试这个:

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

两个隐式转换的参数(Money和DateAsVarchar)都没有显示,但INT,VARCHAR和“真正的”DATE都显示!!! 看看执行计划: enter image description here 这个调用是用GETDATE()完成的。否则只有2个标量运算符......

编辑:执行计划中的第一个“计算标量”显示所有列,常量扫描(扫描带有常量的内部表)只有两列(如果使用GETDATE()则为三列)。在这个阶段,“坏”栏目似乎不是CTE的一部分......

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

再试一次,这会返回预期的结果(空)

我的结论:只处理需要一些额外处理的标量值,因此“知道”它们不应该出现。所有可以在没有任何额外工作的情况下传递的标量值都不会在函数内处理并显示 - 这是一个错误。

你有什么看法?