CTE 和临时表有什么区别?

Rac*_*hel 183 sql-server cte

通用表表达式 (CTE) 和临时表有什么区别?我什么时候应该使用一个?

CTE

WITH cte (Column1, Column2, Column3)
AS
(
    SELECT Column1, Column2, Column3
    FROM SomeTable
)

SELECT * FROM cte
Run Code Online (Sandbox Code Playgroud)

临时表

SELECT Column1, Column2, Column3
INTO #tmpTable
FROM SomeTable

SELECT * FROM #tmpTable
Run Code Online (Sandbox Code Playgroud)

JNK*_*JNK 213

这是相当广泛的,但我会尽可能给你一个一般的答案。

CTE...

  • 不可索引(但可以在引用的对象上使用现有索引)
  • 不能有约束
  • 基本上是一次性VIEW
  • 仅持续到下一个查询运行
  • 可以递归
  • 没有专门的统计数据(依赖底层对象的统计数据)

#临时表...

  • 是存在于 tempdb 中的真实物化表
  • 可编入索引
  • 可以有约束
  • 坚持当前 CONNECTION 的生命周期
  • 可以被其他查询或子过程引用
  • 有引擎生成的专用统计数据

至于何时使用它们,它们有非常不同的用例。如果您将有一个非常大的结果集,或者需要多次引用它,请将其放入#temp表格中。如果它需要递归,是一次性的,或者只是为了逻辑上的简化,aCTE是首选。

此外,CTE永远不会被用于性能。使用 CTE 几乎永远不会加快速度,因为同样,它只是一个一次性视图。你可以用它们做一些巧妙的事情,但加速查询并不是其中之一。

  • 使用 CTE 加速许多查询也是一件事,因为使用 CTE,您可以添加自己的业务知识以超越查询优化器。例如,您可以从您知道结果行非常小的表中选择 CTE 的第 1 部分。在同一个查询中,您可以将这个小结果集加入一些更大的结果集,并完全绕过由陈旧统计信息等引起的问题。为此,您需要添加查询提示以强制排序。它有效,它提高了性能。 (4认同)
  • 使用 CTE 加速大型 MERGE 是一回事 (3认同)
  • “永远不要用于表演”是一个广泛且有点主观的陈述,尽管我理解你的意思。尽管除了其他注释之外,当从另一种递归形式(例如递归过程调用或游标)切换到递归 CTE 时,使用 CTE 可能会带来另一个潜在的性能提升。 (2认同)

jco*_*and 30

编辑:

请参阅下面马丁的评论:

CTE 未具体化为内存中的表。它只是封装查询定义的一种方式。在 OP 的情况下,它将被内联并且与刚刚做的一样SELECT Column1, Column2, Column3 FROM SomeTable。大多数情况下,它们不会预先实现,这就是为什么不返回行的原因WITH T(X) AS (SELECT NEWID())SELECT * FROM T T1 JOIN T T2 ON T1.X=T2.X,还要检查执行计划。尽管有时可能会破坏获得线轴的计划。有一个连接项要求对此提供提示。– 马丁·史密斯 2012 年 2 月 15 日 17:08


原答案

CTE

在 MSDN 上阅读更多内容

CTE 创建在内存中使用的表,但仅对它后面的特定查询有效。使用递归时,这可能是一种有效的结构。

您可能还想考虑使用表变量。这是用来作为一个临时表使用,可以多次使用,而无需重新物化为每个加盟。此外,如果您现在需要保留一些记录,请在下一次选择后添加更多记录,在另一个操作后添加更多记录,然后仅返回那些少数记录,那么这可能是一个方便的结构,因为它不会执行后不需要删除。大多数情况下只是语法糖。但是,如果您保持较低的行数,则它永远不会出现在磁盘上。请参阅SQL Server 中的临时表和表变量有什么区别?更多细节。

临时表

在 MSDN 上阅读更多信息 - 向下滚动大约 40%

临时表实际上是在磁盘上创建的表,只是在每个人都知道的特定数据库中可以删除。好的开发人员有责任在不再需要这些表时销毁它们,但 DBA 也可以擦除它们。

临时表有两种:本地表和全局表。就 MS Sql Server 而言,您使用#tableName本地##tableName名称和全局名称(注意使用单个或双 # 作为标识特征)。

请注意,与表变量或 CTE 不同,临时表可以应用索引等,因为这些是正常意义上的合法表。


一般来说,如果我已经有一个小数据集并且想快速编写一些小代码的脚本,我会使用临时表来进行更长或更大的查询,以及 CTE 或表变量。经验和其他人的建议表明,您应该在返回少量行的情况下使用 CTE。如果您有大量数字,您可能会受益于在临时表上建立索引的能力。

  • CTE 未具体化为内存中的表。它只是封装查询定义的一种方式。在 OP 的情况下,它将被内联,并且与执行`SELECT Column1, Column2, Column3 FROM SomeTable` 相同 (12认同)
  • 大多数情况下它们不会预先实现,这就是为什么这不返回任何行`WITH T(X) AS (SELECT NEWID())SELECT * FROM T T1 JOIN T T2 ON T1.X=T2.X`,还要检查执行计划。虽然有时可以 [hack the plan](http://explainextended.com/2009/05/28/generating-xml-in-subqueries/) 来获得一个线轴。有一个[连接项](https://connect.microsoft.com/SQLServer/feedback/details/218968/provide-a-hint-to-force-intermediate-materialization-of-ctes-or-derived-tables)请求提示。 (5认同)

小智 19

接受的答案在这里说:“一个CTE不应该被用于性能” -但是,这可能会误导。在 CTE 与临时表的上下文中,我刚刚从一组存储过程中删除了一大堆垃圾,因为一些傻瓜一定认为使用临时表几乎没有开销。除了在整个过程中合法地重新使用的那些之外,我把很多东西都塞进了 CTE 。通过所有指标,我获得了大约 20% 的性能。然后我着手删除所有试图实现递归处理的游标。这是我看到最大收获的地方。我最终将响应时间缩短了 10 倍。

CTE 和临时表确实有非常不同的用例。我只想强调,虽然不是万能药,但对 CTE 的理解和正确使用可以在代码质量/可维护性和速度方面带来一些真正的显着改进。因为我掌握了它们,所以我认为临时表和游标是 SQL 处理的大忌。我现在几乎可以使用表变量和 CTE 处理几乎所有内容。我的代码更干净、更快。

  • 你知道编程中的“大恶”是什么吗?当人们说某项技术是邪恶的。有一个放置游标的地方。在某些情况下,它们可以胜过其他技术。这里没有*邪恶* - 您需要学习使用正确的工具来完成工作。衡量你在做什么,不要相信 CTE、临时表或光标是邪恶的炒作。衡量 - 因为真相取决于场景。 (4认同)
  • 根据我的经验,CURSOR 本身并不坏。CURSORS 通常被开发人员“错误地”使用,因为在大多数编程语言中,您必须反复思考,而 SQL 则主要需要批量思考。我知道这是我工作场所的一个常见错误,开发人员除了使用 CURSOR 之外,无法“看到”解决问题的方法,因此为什么优秀的 DBA 可以派上用场来教授和纠正它们。@DaveHilditch 是完全正确的:只需要为正确的工作选择正确的工具即可。 (2认同)

Con*_*lls 16

CTE 可能会在查询中重复调用,并且每次被引用时都会对其进行评估 - 这个过程可以是递归的。如果它只被引用一次,那么它的行为就像一个子查询,尽管 CTE 可以参数化。

临时表是物理持久化的,并且可以被索引。在实践中,查询优化器也可能在幕后持久化中间连接或子查询结果,例如在假脱机操作中,因此严格来说 CTE 的结果永远不会持久化到磁盘上是不正确的。

IIRC 表变量(另一方面)始终是内存中的结构。

  • CTE 可以参数化吗?如何?此外,表变量并非*总是*内存结构。请参阅 Martin 对相关问题的 [优秀回答](http://dba.stackexchange.com/a/16386/1192)。 (6认同)

Ole*_*Dok 12

临时表是 tempdb 中的一个真实对象,但 cte 只是一种复杂查询的包装器,以一步简化组织递归的语法。


Dav*_*tch 10

使用 CTE 的主要原因是访问窗口函数row_number()和其他各种函数。

这意味着您可以非常快速和高效地执行诸如获取每个组的第一行或最后一行之类的操作 -在大多数实际情况下比其他方式更有效

with reallyfastcte as (
select *, 
row_number() over (partition by groupingcolumn order by sortingcolumn) as rownum
from sometable
)
select *
from reallyfastcte
where rownum = 1;
Run Code Online (Sandbox Code Playgroud)

您可以使用相关子查询或使用子查询运行与上述类似的查询,但 CTE 在几乎所有情况下都会更快。

此外,CTE 确实可以帮助简化您的代码。这可以带来性能提升,因为您更了解查询并且可以引入更多业务逻辑来帮助优化器更具选择性。

此外,如果您了解您的业务逻辑并知道应该首先运行查询的哪些部分,CTE 可以提高性能 - 通常,将最具选择性的查询放在最前面,从而导致结果集可以在下一次连接中使用索引并添加option(force order)查询暗示

最后,CTE 默认不使用 tempdb,因此您可以通过使用它们来减少对该瓶颈的争用。

如果您需要多次查询数据,或者如果您测量查询并发现通过插入到临时表然后添加索引来提高性能,则应使用临时表。


小智 7

这里似乎对 CTE 有点消极。

我对 CTE 的理解是它基本上是一种临时视图。SQL 既是一种声明性语言,又是一种基于集合的语言。CTE 是声明集合的好方法!不能索引 CTE 实际上是一件好事,因为您不需要!它确实是一种使查询更易于读/写的语法糖。任何体面的优化器都会使用基础表上的索引来制定最佳访问计划。这意味着您可以通过遵循基础表上的索引建议来有效地加速 CTE 查询。

此外,仅仅因为您将集合定义为 CTE,并不意味着必须处理集合中的所有行。根据查询,优化器可能会处理“刚好足够”的行以满足查询。也许你的屏幕只需要前 20 个左右。如果您构建了一个临时表,那么您确实需要读取/写入所有这些行!

基于此,我会说 CTE 是 SQL 的一项重要功能,可以在任何使查询更易于阅读的地方使用。我只会考虑一个真正需要处理每条记录的批处理的临时表。即使这样 afaik 也不是真正推荐它,因为在临时表上,数据库很难帮助您进行缓存和索引。最好有一个永久表,其中包含对您的事务唯一的 PK 字段。

我不得不承认,我的经验主要是在 DB2 方面,所以我假设 CTE 在这两种产品中的工作方式相似。如果 CTE 在 SQL 服务器中稍逊一筹,我会很乐意接受纠正。;)