视情况而定。
首先
什么是公用表表达式?
A(非递归)CTE 的处理方式与其他可用作 SQL Server 中的内联表表达式的构造非常相似。派生表、视图和内联表值函数。请注意,虽然 BOL 说 CTE“可以被认为是临时结果集”,但这是一个纯粹的逻辑描述。通常情况下,它本身并没有具体化。
什么是临时表?
这是存储在 tempdb 数据页上的行集合。数据页可以部分或全部驻留在内存中。此外,临时表可能会被索引并具有列统计信息。
测试数据
CREATE TABLE T(A INT IDENTITY PRIMARY KEY, B INT , F CHAR(8000) NULL);
INSERT INTO T(B)
SELECT TOP (1000000) 0 + CAST(NEWID() AS BINARY(4))
FROM master..spt_values v1,
master..spt_values v2;
示例 1
WITH CTE1 AS
(
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
)
SELECT *
FROM CTE1
WHERE A = 780
请注意,上面的计划中没有提到 CTE1。它只是直接访问基表并被视为相同
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
WHERE A = 780
通过在此处将 CTE 实体化为中间临时表进行重写将大大适得其反。
具体化 CTE 的定义
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
将涉及将大约 8GB 的数据复制到临时表中,然后仍然存在从中进行选择的开销。
示例 2
WITH CTE2
AS (SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0)
SELECT *
FROM CTE2 T1
CROSS APPLY (SELECT TOP (1) *
FROM CTE2 T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
上面的例子在我的机器上大约需要 4 分钟。
在 1,000,000 个随机生成的值中,只有 15 行与谓词匹配,但代价高昂的表扫描发生了 16 次以找到这些值。
这将是实现中间结果的理想选择。等效的临时表重写需要 25 秒。
INSERT INTO #T
SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0
SELECT *
FROM #T T1
CROSS APPLY (SELECT TOP (1) *
FROM #T T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
将查询的一部分中间具体化到临时表中有时会很有用,即使它只评估一次 - 当它允许利用具体化结果的统计信息重新编译查询的其余部分时。 SQL Cat 文章When To Break Down Complex Queries 中提供了这种方法的一个示例。
在某些情况下,SQL Server 会使用假脱机来缓存中间结果,例如的 CTE,并避免重新评估该子树。这在(迁移的)连接项Provide a hint to force intermediate materialization of CTEs or derived tables 中进行了讨论。但是,没有对此创建统计信息,即使假脱机行数与估计的有很大不同,正在进行的执行计划也无法动态适应响应(至少在当前版本中。自适应查询计划可能会在未来)。