Luk*_*ith 3 sql sql-server common-table-expression
我正在考虑提高某些SQL的性能,目前CTE在脚本中被多次使用和引用.我会使用表变量来改进吗?(不能使用临时表,因为代码在函数内).
CTE只不过是语法糖。
它增强了可读性并可以避免重复。
只需将其视为WITH()- 子句中指定的实际语句的占位符即可。引擎将使用此语句替换查询中出现的任何 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 极其智能的引擎将找到获得您所要求的集合的最佳方法。在上述情况下,所有结果描述都指向同一目标。引擎可以从各种语句中推断出这一点,并为所有语句找到相同的计划。
嗯,这只是品味问题吗?
在某种方式...
有一些重要的事情需要记住:
VIEWs,都使用CTEs 并互相调用......TRY_CAST. 这个想法是为了确保下面的查询中的值有效。但引擎想:“哦,只是一个 CAST,不贵!” 并将实际 CAST 包含到更高位置的执行计划中。我记得另一个例子,引擎对数百万行执行了昂贵的操作(不必要地,最终结果被过滤到一个很小的集合),只是因为实际的执行顺序不符合预期。好的...那么我什么时候应该使用 CTE?
以下几点是使用 CTE 的充分理由:
JOIN具有动态行为的语句中使用,具体取决于实际行数。CROSS JOIN轻松地将它们放入您的查询中。DELETE. 最后提示
嗯,在某些情况下,丑陋的代码表现更好:-)
拥有干净且可读的代码总是好的。CTE 将帮助您解决此问题。所以试试吧。如果性能不好,请深入研究执行计划,并尝试找到引擎可能做出错误决定的原因。
在大多数情况下,尝试通过以下提示来智取引擎是一个坏主意FORCE ORDER(但可以提供帮助)
更新
我被要求具体指出优点和缺点:
嗯,从技术上讲,没有什么真正的优点或缺点。不考虑递归 CTE,没有 CTE 就没有解决不了的问题。
优点
主要优点是可读性和可维护性。
有时一个 CTE 可以节省数百行代码。可以仅使用名称作为变量,而不是重复巨大的子查询。对子查询的更正可以仅在一处解决。
CTE 可以提供临时查询服务,让您的生活更轻松。
缺点
一个可能的缺点是,即使对于经验丰富的开发人员来说,也很容易将 CTE 误认为临时表,假设可见的步骤顺序与实际的执行顺序相同,并陷入意外结果甚至错误。- 当然:-) -wrong syntax当您在另一个语句后编写 CTE 而不使用分隔符时,您会看到奇怪的错误;。这就是为什么很多人倾向于使用;WITH.
你真的要进行性能测试 - 没有是/否答案.根据Andy Living上面链接的帖子,CTE只是查询或子查询的简写.
如果您在同一个函数中调用它两次或更多次,那么如果填充表变量然后加入/选择它,可能会获得更好的性能.但是,由于表变量在某处占用空间,并且没有索引/统计信息(除了表变量上的任何声明的主键),没有办法说哪个更快.
它们都有成本和节省,这是最好的方式取决于它们引入的数据以及它们使用它们做了什么.我一直在你的情况下,在各种条件下测试速度后 - 一些函数使用CTE,其他函数使用表变量.