CROSS APPLY 风格与性能

Posted

技术标签:

【中文标题】CROSS APPLY 风格与性能【英文标题】:CROSS APPLY Style versus Performance 【发布时间】:2012-07-10 18:14:18 【问题描述】:

我应该为每个别名表达式使用单独的 CROSS APPLY 还是尽可能在同一个 CROSS APPLY 中定义多个表达式?

我正在重构代码中生成的许多非常复杂的 SQL 查询(通常是四五页长的十几个 JOIN)。通常它们嵌套了五六个查询深度,因为某些部分在不同情况下被重用。嵌套查询的一个典型原因是一个查询提取一些值,执行计算,并为该值分配一个别名。然后封闭查询使用别名值等进行进一步计算。这是必要的,因为别名表达式只能在外部查询中引用,而不能在定义它的查询中的其他地方引用。 (另一种方法,也存在于代码中,是在整个查询中重复同一个冗长的子表达式十几次。)

为了简化这个意大利面条式 SQL,我开始使用 CROSS APPLY 折叠这些伸缩式查询。通过使用 CROSS APPLY,我可以分配一个别名并在同一个查询的其他地方使用它。请注意,我的 CROSS APPLY 子句都没有通过子选择或调用 UDF 引入新表。

一开始,我把很多表达式放在同一个CROSS APPLY里。但是,我的代码需要模块化。在不同的情况下需要不同的字段,因为用户可以为过滤器选择字段,并且我们为不同的列生成相似的统计信息,因此创建了许多相似的查询,它们彼此之间只有一点点不同。由于有些字段建立在其他字段之上,而有些则依赖于查询中前面定义的某些 JOIN,因此我不能将所有内容放在同一个 CROSS APPLY 中。因此,CROSS APPLY 的数量可能会有所不同。

此外,一些从中提取的字段被新别名遮蔽并重新定义(通常是为了删除 NULL、提供默认值等)。因此,当在其他地方引用 CROSS APPLY 定义的字段时,CROSS APPLY 的表别名将需要被指定以消除歧义。

这样做的结果是,我需要一种合乎逻辑的方式来命名我的 CROSS APPLY,并且我关心性能。如果 CROSS APPLY 的数量发生变化,那么如果 CROSS APPLY 使用简单的计数器进行编号(例如,computed1、computed2 等),那么给定字段可能会看到其名称引用从 computed3.[FIELD NAME] 更改为 computed4.[FIELD NAME],它在查询的其余部分产生连锁反应,可能是通过调用不同的 C# 过程来组装的。这是一个维护问题。

我正在考虑的替代方法是将每个计算表达式放在单独的 CROSS APPLY 中,并且 CROSS APPLY 的名称将从表达式的别名派生。例如:

使用一个 CROSS APPLY:

CROSS APPLY (
    SELECT
          [TIV_BLDG] = (CASE [COVERAGE] WHEN 'TIV_BLDG' THEN VALUEAMT ELSE 0 END)

        , [TIV_OSTR] = (CASE [COVERAGE] WHEN 'TIV_OSTR' THEN VALUEAMT ELSE 0 END)

        , [TIV_CONT] = (CASE [COVERAGE] WHEN 'TIV_CONT' THEN VALUEAMT ELSE 0 END)

        , [TIV_TIME] = (CASE [COVERAGE] WHEN 'TIV_TIME' THEN VALUEAMT ELSE 0 END)

) computed2

使用四个 CROSS APPLY:

CROSS APPLY (SELECT [TIV_BLDG] = (CASE [COVERAGE] WHEN 'TIV_BLDG' THEN VALUEAMT ELSE 0 END)) AS [TIV_BLDG_COMP]

CROSS APPLY (SELECT [TIV_OSTR] = (CASE [COVERAGE] WHEN 'TIV_OSTR' THEN VALUEAMT ELSE 0 END)) AS [TIV_OSTR_COMP]

CROSS APPLY (SELECT [TIV_CONT] = (CASE [COVERAGE] WHEN 'TIV_CONT' THEN VALUEAMT ELSE 0 END)) AS [TIV_CONT_COMP]

CROSS APPLY (SELECT [TIV_TIME] = (CASE [COVERAGE] WHEN 'TIV_TIME' THEN VALUEAMT ELSE 0 END)) AS [TIV_TIME_COMP]

如果我使用这种多重 CROSS APPLY 方法,我可以轻松生成在添加新应用或删除它们时不会更改的名称,并且我可以在引用字段的代码中预测表别名将是什么。然而,这意味着更多的 CROSS APPLY 语句。性能影响是什么?有没有更好的方法来做到这一点,使用 CROSS APPLY 或其他一些 SQL Server 功能? (我不需要它是跨数据库的,所以只有 Microsoft 的功能就可以了。)

【问题讨论】:

Nit:查询规划器应该能够在 RA 规则中很好地折叠“额外别名”(例如,完全从计划中删除它们)。它甚至应该删除未引用的选定值并将这个事实下推等。在任何情况下,查询计划应该是第一个寻找性能瓶颈的地方 无论如何,我使用了一些自动化的 SQL 生成(没有交叉应用,但有许多嵌套连接和一些联合等)。为了处理列名更改,我还通过每个选择跟踪列名(我返回一个表示列名目标表/别名的结构)。然后在每个级别,我根据新选择的“视图”重新生成结构,并更新/丢弃列和目标表/别名。使用这种方法,我避免了级别之间别名的大部分硬编码,并允许最终/外部查询仅选择适当的内部列。 SQL Server 喜欢它。 【参考方案1】:

如果存在性能劣势,则必须在查询执行的编译阶段。由于执行计划在查询执行的第二秒被缓存,因此不会有性能损失。

SQL Server 可以看穿 CROSS APPLY 并将它们转换为简单的堆叠“计算标量”。

另一个潜在问题可能是查询的大小。不确定在 AST 节点或连接的数量方面是否有任何相关限制(我猜 CROSS APPLY 在涉及内部限制时算作连接)。

除了这些问题,我认为 CROSS APPLYs 是一个非常好的和快速的解决方案。

【讨论】:

以上是关于CROSS APPLY 风格与性能的主要内容,如果未能解决你的问题,请参考以下文章

具有表值函数限制性能的 CROSS APPLY

TensorFlow基础笔记 图像风格化效果与性能优化

.NET 中 GC 的模式与风格

.NET 中 GC 的模式与风格

使用 CROSS APPLY 与 OUTER APPLY 连接查询

使用 CROSS APPLY 与 OUTER APPLY 连接查询