使用CTE有哪些优点/缺点?

Luk*_*ith 3 sql sql-server common-table-expression

我正在考虑提高某些SQL的性能,目前CTE在脚本中被多次使用和引用.我会使用表变量来改进吗?(不能使用临时表,因为代码在函数内).

Shn*_*ugo 7

CTE只不过是语法增强了可读性可以避免重复

只需将其视为WITH()- 子句中指定的实际语句的占位符即可。引擎将使用此语句替换查询中出现的任何 CTE 名称(与视图非常相似)。这就是内联的意义。

与之前填写的表格(声明或创建)相比,您会发现优点

  • 可用于临时查询(函数、视图)
  • 没有意外的副作用(范围最窄)

...以及缺点

  • 您不能在不同的语句中使用 CTE 的结果
  • 您不能使用索引、统计信息来优化 CTE 集(尽管它会隐式使用目标对象的现有索引和统计信息 - 如果适用)。

性能而言,持久集(声明或创建的表)在某些情况下可能(好得多!)更好,但它迫使您使用过程代码。你必须让你的马赛跑,找出哪匹马更好......

示例:执行相同操作的各种方法

以下简单(相当无用)的示例描述了一组用户表及其列。我使用各种不同的方法来告诉 SQL-Server 我想要什么:

尝试使用“包括实际执行计划”

USE master; --in my case the master database has just 5 "user tables", you can use any other DB of course
GO
    
--simple join, first the small set joining to the large set
SELECT o.name AS TableName
      ,c.name AS ColumnName
FROM sys.objects o
INNER JOIN sys.columns c ON c.object_id=o.object_id 
WHERE o.type='U';
GO

--simple join "the other way round" with the filter as part of the ON-clause
SELECT o.name AS TableName
      ,c.name AS ColumnName
FROM sys.columns c
INNER JOIN sys.objects o ON c.object_id=o.object_id AND o.type='U';
GO

--join from the large set with a sub-query to the small set
SELECT o.name AS TableName
      ,c.name AS ColumnName
FROM sys.columns c
INNER JOIN (
    SELECT o.* 
    FROM sys.objects o
    WHERE o.type='U' --user tables 
) o ON c.object_id=o.object_id;
GO

--join for large to small with a row-wise APPLY
SELECT o.name AS TableName
      ,c.name AS ColumnName
FROM sys.columns c
CROSS APPLY (
    SELECT o.* 
    FROM sys.objects o
    WHERE o.type='U' --user tables 
    AND o.object_id=c.object_id
) o;
GO

--use a CTE to "pre-filter" the small set
WITH cte AS
(
    SELECT o.* 
    FROM sys.objects o
    WHERE o.type='U' --user tables 
)
SELECT cte.name AS TableName
      ,c.name AS ColumnName
FROM sys.columns c
INNER JOIN cte ON c.object_id=cte.object_id;
GO
Run Code Online (Sandbox Code Playgroud)

现在看看结果和执行计划:

  • 所有查询返回相同的结果。
  • 所有查询产生相同的执行计划

重要提示:这可能在您的机器上有所不同!

为什么是这样?

T-SQL 是一种声明性语言。您的声明是对您想要检索的内容的描述。告诉引擎这是如何完成的不是你的工作。

SQL-Server 极其智能的引擎将找到获得您所要求的集合的最佳方法。在上述情况下,所有结果描述都指向同一目标。引擎可以从各种语句中推断出这一点,并为所有语句找到相同的计划。

嗯,这只是品味问题吗?

在某种方式...

有一些重要的事情需要记住:

  • 引擎没有理由在其余结果之前计算 CTE 结果(尽管该语句可能看起来如此)。因此,将 CTE 描述为类似临时表的东西错误的......
  • 换句话说:语句的可见顺序并不能预测实际的执行顺序
  • 智能引擎将在复杂性和嵌套级别上达到其极限。想象一下各种VIEWs,都使用CTEs 并互相调用......
  • 在某些情况下,引擎真的会出故障。我记得有一个案例,CTE 的作用并不比TRY_CAST. 这个想法是为了确保下面的查询中的值有效。但引擎想:“哦,只是一个 CAST,不贵!” 并将实际 CAST 包含到更高位置的执行计划中。我记得另一个例子,引擎对数百万行执行了昂贵的操作(不必要地,最终结果被过滤到一个很小的集合),只是因为实际的执行顺序不符合预期。

好的...那么我什么时候应该使用 CTE?

以下几点是使用 CTE 的充分理由:

  • CTE 可以帮助您避免重复的子查询。
  • CTE 可以在语句中多次使用,例如在JOIN具有动态行为的语句中使用,具体取决于实际行数。
  • 您可以在一条语句中使用多个 CTE,并且可以在后续 CTE 中使用一个 CTE 的结果。
  • 有递归(或更好的迭代)CTE。
  • 有时我使用单行-CTE 来定义/预计算稍后在查询中使用的变量。您将在过程 T-SQL 中使用声明的变量执行的操作。您可以使用 ACROSS JOIN轻松地将它们放入您的查询中。
  • 而且也非常好:可更新的 CTE允许非常易于阅读的语句,这同样适用DELETE.
    如上所述:没有 CTE,没有什么是做不到的,但阅读要好得多(我真的很喜欢说出名字)。

最后提示

嗯,在某些情况下,丑陋的代码表现更好:-)

拥有干净且可读的代码总是好的。CTE 将帮助您解决此问题。所以试试吧。如果性能不好,请深入研究执行计划,并尝试找到引擎可能做出错误决定的原因。

在大多数情况下,尝试通过以下提示来智取引擎是一个坏主意FORCE ORDER(但可以提供帮助)

更新

我被要求具体指出优点和缺点:

嗯,从技术上讲,没有什么真正的优点或缺点。不考虑递归 CTE,没有 CTE 就没有解决不了的问题。

优点
主要优点可读性和可维护性
有时一个 CTE 可以节省数百行代码。可以仅使用名称作为变量,而不是重复巨大的子查询。对子查询的更正可以仅在一处解决。
CTE 可以提供临时查询服务,让您的生活更轻松。

缺点
一个可能的缺点是,即使对于经验丰富的开发人员来说,也很容易将 CTE 误认为临时表,假设可见的步骤顺序与实际的执行顺序相同,并陷入意外结果甚至错误。- 当然:-) -wrong syntax当您在另一个语句后编写 CTE 而不使用分隔符时,您会看到奇怪的错误;。这就是为什么很多人倾向于使用;WITH.


Mef*_*eff 6

你真的要进行性能测试 - 没有是/否答案.根据Andy Living上面链接的帖子,CTE只是查询或子查询的简写.

如果您在同一个函数中调用它两次或更多次,那么如果填充表变量然后加入/选择它,可能会获得更好的性能.但是,由于表变量在某处占用空间,并且没有索引/统计信息(除了表变量上的任何声明的主键),没有办法说哪个更快.

它们都有成本和节省,这是最好的方式取决于它们引入的数据以及它们使用它们做了什么.我一直在你的情况下,在各种条件下测试速度后 - 一些函数使用CTE,其他函数使用表变量.