我试图在同一个查询中使用不同参数两次调用TVF,但由于某种原因,当我将结果加在一起时,其中一个结果会掩盖另一个结果。我已将问题减少到这个小例子:
采取这个内联TVF:
CREATE FUNCTION dbo.fnTestErrorInline(@Test INT)
RETURNS TABLE
AS
RETURN
(
SELECT ID, Val
FROM (VALUES
(1, 1, 10),
(1, 2, 20),
(1, 3, 30),
(1, 4, 40),
(2, 1, 50),
(2, 2, 60),
(2, 3, 70),
(2, 4, 80)
) t(Test, ID, Val)
WHERE t.Test=@Test
)
和等效的多线函数:
CREATE FUNCTION dbo.fnTestErrorMultiline(@Test INT)
RETURNS @tbl TABLE (
ID INT NOT NULL,
Val INT NOT NULL
)
AS
BEGIN
IF @Test=1
INSERT INTO @tbl (ID, Val) VALUES
(1, 10),
(2, 20),
(3, 30),
(4, 40);
IF @Test=2
INSERT INTO @tbl (ID, Val) VALUES
(1, 50),
(2, 60),
(3, 70),
(4, 80);
RETURN
END
如果我运行此查询:
WITH cte1 AS (
SELECT ID, SUM(Val) AS Total
FROM dbo.fnTestErrorInline(1)
GROUP BY ID
), cte2 AS (
SELECT ID, SUM(Val) AS Total
FROM dbo.fnTestErrorInline(2)
GROUP BY ID
)
SELECT *
FROM cte1 c1
INNER JOIN cte2 c2 ON c1.ID=c2.ID;
结果如预期:
ID Total ID Total
1 10 1 50
2 20 2 60
3 30 3 70
4 40 4 80
但是当我使用函数的多行版本时:
WITH cte1 AS (
SELECT ID, SUM(Val) AS Total
FROM dbo.fnTestErrorMultiline(1)
GROUP BY ID
), cte2 AS (
SELECT ID, SUM(Val) AS Total
FROM dbo.fnTestErrorMultiline(2)
GROUP BY ID
)
SELECT *
FROM cte1 c1
INNER JOIN cte2 c2 ON c1.ID=c2.ID;
结果不正确 - cte2显示与cte1相同的值:
ID Total ID Total
1 10 1 10
2 20 2 20
3 30 3 30
4 40 4 40
此外,我只在GROUP BY
出现时才会看到此行为。没有它,结果很好。
奇怪的是,如果我向第二个CTE添加另一列,它会改变结果:
WITH cte1 AS (
SELECT ID, SUM(Val) AS Total
FROM dbo.fnTestErrorMultiline(1)
GROUP BY ID
), cte2 AS (
SELECT ID, SUM(Val) AS Total, SUM(Val+0) AS why
FROM dbo.fnTestErrorMultiline(2)
GROUP BY ID
)
SELECT *
FROM cte1 c1
INNER JOIN cte2 c2 ON c1.ID=c2.ID;
产量
ID Total ID Total why
1 50 1 50 50
2 60 2 60 60
3 70 3 70 70
4 80 4 80 80
额外的列似乎需要引用TVF表中的一列 - 一个常数值不会改变结果。
这里发生了什么?您是不是应该每次查询多次呼叫多线TVF?
我已经在SQL Server 2008 R2和2012上对此进行了测试
答案 0 :(得分:7)
这是SQL Server中的一个已知错误,它可能会错误地将一个TVF实例的结果假脱机并为另一个实例重放它们(尽管另一个具有不同的参数并返回不同的结果)。
该错误已经存在了一段时间,但最近基数估算的变化意味着在2014年以后它甚至更有可能遇到这个问题。
请参阅连接项..
注意:执行计划如下所示。
它使用Common Subexpression Spool所有三个突出显示的线轴实际上都是同一个对象,在黄色运算符中插入行,然后在绿色运算符中重放它们。
添加
OPTION (QUERYRULEOFF GenGbApplySimple, QUERYRULEOFF BuildGbApply)
避免了这个问题并给出了一个不同的计划,但是这不是我在生产中使用的。