CTE和临时表之间有什么区别?


174

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

CTE

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

SELECT * FROM cte

临时表

SELECT Column1, Column2, Column3
INTO #tmpTable
FROM SomeTable

SELECT * FROM #tmpTable


Answers:


200

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

CTE ...

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

#临时表...

  • 是tempdb中存在的真实实例化表
  • 可以索引
  • 可以有约束
  • 持续当前连接的生命
  • 可以被其他查询或子过程引用
  • 具有引擎生成的专用统计信息

至于何时使用它们,它们有非常不同的用例。如果结果集很大,或者需要多次引用它,请将其放在#temp表中。如果需要递归,可抛弃或者只是为了逻辑上的简化,CTE则首选a。

此外,CTE永远不会被用于性能。您几乎永远不会通过使用CTE来加快处理速度,因为同样,它只是一个一次性视图。您可以对它们进行一些整洁的操作,但加快查询的速度并不是真正的其中之一。


使用CTE加速大型MERGE是一件事情
AgentFire '18

1
使用CTE加速许多查询也是一件事情,因为使用CTE,您可以添加自己的业务知识,从而胜过查询优化器。例如,您可以从表中选择CTE的第1部分,在这些表中您知道结果行将非常小。在同一个查询中,您可以将此微小的结果集加入到更大的结果集中,并完全绕开由过时的统计信息等引起的问题。为此,您需要添加查询提示以强制执行顺序。它有效,可以提高性能。
戴夫·希尔迪奇

尽管我理解您的观点,但“绝不用于表现”是一个广泛的主观陈述。尽管除了其他注释之外,当从另一种形式的递归(例如递归过程调用或游标)切换到递归CTE时,使用CTE可能会带来其他潜在的性能提升。
JD

29

编辑:

请在下面查看马丁的评论:

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,还检查执行计划。尽管有时可以破解该计划以获取假脱机。有一个连接项要求对此进行提示。–马丁·史密斯(Martin Smith)2012年2月15日在17:08


原始答案

CTE

阅读有关MSDN的更多信息

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

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

临时表

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

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

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

请注意,与临时表相比,与表变量或CTE相比,您可以应用索引等,因为从字面意义上来说,它们是合法的表。


通常,如果我已经有一个很小的数据集,并且想要快速编写一些小的代码,我会使用临时表来处理更长或更长时间的查询,并使用CTE或表变量。经验和其他人的建议表明,如果从中返回少量行,则应使用CTE。如果数量很多,您可能会受益于在临时表上建立索引的功能。


11
CTE未在内存中实现为表格。这只是封装查询定义的一种方法。在OP的情况下,它将被内联并且与执行操作相同SELECT Column1, Column2, Column3 FROM SomeTable
Martin Smith

4
在大多数情况下,它们不会预先实现,这就是为什么它不返回任何行的原因WITH T(X) AS (SELECT NEWID())SELECT * FROM T T1 JOIN T T2 ON T1.X=T2.X,还检查执行计划。尽管有时可以破解该计划以获取假脱机。有一个连接项要求对此进行提示。
马丁·史密斯

16

接受的答案在这里说:“一个CTE不应该被用于性能” -但是,这可能会误导。在CTE与临时表的上下文中,我刚刚从一组存储的proc中删除了一大堆垃圾,因为有些痴迷一定认为使用临时表几乎没有开销。我将大量精力投入到CTE中,除了那些在整个过程中可以合理使用的CTE 。从所有指标来看,我获得了约20%的效果。然后,我开始着手删除所有试图实现递归处理的游标。这是我收获最大的地方。我最终将响应时间缩短了十倍。

CTE和临时表确实有非常不同的用例。我只是想强调一点,尽管不是万能药,但CTE的理解和正确使用可以在代码质量/可维护性和速度方面带来真正真正的进步。自从有了它们的句柄以来,我将临时表和游标视为SQL处理的最大弊端。现在几乎可以使用表变量和CTE了。我的代码更干净,更快。


现在,公平地说-游标是最大的祸害。临时表在最坏的情况下要小一些。:-) 就像您自己看到的那样,将它们置于同一水平上确实是不公平的。
RDFozz

@RDFozz对,众所周知,地狱有9个圈子。让我们将临时表放在第二位,将游标放在...第七位?;)
ypercubeᵀᴹ18年

1
您知道编程中的“大恶魔”是什么吗?当人们说一种特定的技术是邪恶的。有一个游标的地方。在某些情况下,它们可以胜过其他技术。这里没有邪恶 -您需要学习使用正确的工具来完成工作。衡量您在做什么,不要相信CTE,临时表或游标是邪恶的炒作。量度-因为事实取决于情况。
戴夫·希尔迪奇

@DaveHilditch是一个很公平的评论,但是断言在很多情况下游标不是正确的解决方案也是一个公平的评论,因此将它们作为一个几乎是最后的选择是一个可行的概括。
梅尔·帕登

1
以我的经验,游标本身并不坏。CURSORS通常被开发人员“错误地”使用,因为在大多数编程语言中,您必须迭代地思考,而不是在SQL中,您通常必须分批思考。我知道这是我工作场所中的一个常见错误,开发人员只是用CURSOR不能“看到”解决问题的方法,因此,为什么一个好的DBA可以派上用场了。@DaveHilditch是完全正确的:完成正确工作所需的正确工具。
菲利普

14

CTE可能会在查询中重复调用,并且每次引用CTE都会对其进行评估-此过程可以递归进行。如果仅引用一次它,则它的行为很像子查询,尽管可以对CTE进行参数化。

临时表在物理上是持久的,并且可以建立索引。在实践中,查询优化器还可以在后台(例如在后台打印操作中)持久保留中间联接或子查询的结果,因此严格来讲,CTE的结果永远不会持久保存到磁盘中并不是完全正确的。

另一方面,IIRC表变量始终是内存结构。


4
CTE是否可以参数化?怎么样?另外,表变量并不总是内存结构。请参阅马丁对相关问题的出色回答
保罗·怀特


8

使用CTE的主要原因是要访问Window函数(例如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;

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

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

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

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

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


所有优点... +1
梅尔·帕登

6

这里对CTE的看法似乎有些消极。

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

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

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

我不得不承认我的经验主要是在DB2上,因​​此我假设两种产品中CTE的工作方式都相似。如果CTE在SQL Server中不如亚特兰大,我会很乐意纠正。;)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.