WinMagic : Subquery Elimination Using Window Aggregation

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 这篇精干的paper介绍了IBM DB2基于window function对相关子查询进行解相关的等价变换。

这篇精干的paper介绍了IBM DB2基于window function对相关子查询进行解相关的等价变换。

基本概念

例如SQL:

SELECT emp_id, emp_name, dept_name
FROM employee E, department D
WHERE E.dept_num = D.dept_num AND
E.state = ‘CALIFORNIA’ AND
E.salary > (SELECT AVG(salary)
FROM employee E1
WHERE E1.dept_num = D.dept_num);

其原始的语义就是对外层查询的每一行结果(E join D),获取其相关列D.dept_num的值(d),代入到相关子查询中,内层所有在E1.dept_num上具有相同对应值(d)的行,作为满足相关条件的过滤结果,计算一次聚集。然后基于聚集的结果对外层的该行进行过滤判断(E.salary > AVG(sal))。

DB2在之前的paper中提到了一种magic de-correlation的变换,利用了所谓的magic set。根据相关子查询的语义,将外表相关列的每一个唯一值组成一个magic set,拉入到内层来与内表join,join的结果其实就等同于外表每个相关值所对应的内表行,组合在了一起。这样在对各个组各自聚集,聚集的结果是以外表的相关列为分组的,因此就完成了去相关,计算结果再与外表join就可以了。

v2-50dd0eb74f20f9d9d5945f4122d2b318_b.png

从上图可以看到,提取出magic set与内表做join的过程,就等价于用外表每一个相关值在内表依次进行过滤的过程,这样计算出的每个分组聚集结果就是每次带入相关value计算的聚集结果。将结果与外层再基于相关列做join,等于还原了针对外表每一行都要计算的语义,恢复原始结果。

WinMagic也是这个思路,只是在满足某些特定条件下,可以更进一步,去掉内/外层中的common table,使其只需要计算一次就可以了。

关于window function的介绍这里就不赘述了,可以参考MySQL reference的介绍 。这里就先假设大家都了解window function了。

WinMagic Transformation

前提条件

  1. 子查询中有aggregation,这样window function才有意义。
  2. 由于改变了整个查询的执行流程,不能存在有side effect的函数,也就是在不同执行流程下结果会不同的函数,例如RAND()这样的function。
  3. subquery中不能有Top-N这种截断性的操作,会破坏内外层数据的一致性
  4. 内层aggregation具有等价的window function,例如min/max/sum/count,而没有aggr(distinct ...)的聚集计算(window function不支持distinct)。
  5. 外层和内层之间,存在subsume关系!!(外层在join table/condition上,包含内层),这个条件可以放宽:

内层如果有其他非相关表,必须是lossless join,保证内层相关表的数据不会由于join丢失/增加

内层相关表可以有更多的单表限定条件,在转换时,需要通过在window aggregation时,利用CASE …. WHEN这样的条件判断,来保证数据能够按照原语义被过滤。

关于这种特殊情况,我们一会可以通过示例看下。

转换过程

  1. 内层aggregation -> window aggregation,并以内层相关列作为partition by列。
  2. 把外层相关表压入到内层中,与内层表做join,这是在解相关,但这里要区分对待:

2.1 如果外层相关列是个主键/唯一键列,则拉入内层,也不会导致内表数据量增加(内层每一行,最多join上外层一行),由于外层中也有这个内层表以及表上的过滤条件(subsume关系)。内层过滤掉的数据外层也一样过滤掉了,数据内外是一致的,这时window partition列是[内层相关列]

2.2 外层相关列不是主键/唯一列,拉入内层后,内层表数据量会由于join而变多(重复的外层相关列),此时仍需要保证”对外表每一行,内表做一次聚集“的语义,因此window partition列是 [外表主键,内层相关列]。

3. 由于外层subsume内层,而且内层包含了所有common table,这时外层的common table就可以去掉了,只剩下非相关table,再与子查询去相关后的derived table做join。这样就去掉了对common table的重复执行。

用一个具体的示例来说明这个过程:

SELECT SUM(l_extendedprice) / 7.0 AS avg_yearly
FROM tpcd.lineitem, tpcd.part
WHERE p_partkey = l_partkey AND
p_brand = 'Brand#23' AND
p_container = 'MED BOX' AND
l_quantity <
(SELECT 0.2*avg(l_quantity)
 FROM tpcd.lineitem
 WHERE l_partkey = p_partkey);

观察以上query,内外层都包含tpcd.lineitem这个表,相关条件在l_partkey = p_partkey。

image.png

如上示例符合2.1中的情况,即相关列是外层表part的主键列,因此window function只需要partition by p_partkey。

1将外层的part推入内层,2与lineitem做join,等于在外层/内层,都有lineitem join part这个操作,产生了相同的结果集(外层由于可能有更多条件,可能数据会更少,但没有关系,可以把这些额外条件认为在join之后再计算)。3在join结果上基于相关列做partitioned window function的计算,实际上就是基于每个相关值,计算一个局部聚集结果。4将聚集结果作为附加列添加在join结果的最后。5由于内外层subsume的关系,内层join的结果也是外层join的结果,并附加了基于相关性计算的内层聚集结果作为额外一列。这时再应用外层其他条件,对结果继续过滤就可以了。

这里把外层推入后,最主要的一个优化就是内层part join lineitem的结果就是外层common tables join的结果。因此只需要在内层计算一次,结果以derived table的形式复用到外层就可以了,避免了内外层的重复计算。

转换后的query变为

WITH WinMagic AS
(SELECT l_extendedprice, l_quantity,
avg(l_quantity)over(partition by p_partkey)
AS avg_l_quantity
FROM tpcd.lineitem, tpcd.part
WHERE p_partkey = l_partkey and
p_brand = 'Brand#23' and
p_container = 'MED BOX' )
SELECT SUM(l_extendedprice) / 7.0 as avg_yearly
FROM WinMagic
WHERE l_quantity < 0.2 * avg_l_quantity;

对于2.2的情况,思路是完全一样的,只是原始语义中,是外层的一行带入到内层做一次聚集,因此应该仍保证这一点,在内层算window aggregation时考虑外层主键 + 内层相关列。

前面提到了如果内层有比外层更多的过滤条件怎么办?仍然从语义的角度出发,也就是在内层做join时,有些行会被内层的额外条件过滤掉,但是为了满足内外层数据一致的特性,仍然是需要原样做join的,只是那些过滤掉的行不参与window aggregation的计算就可以了。

可以通过类似sum(case when pred is true then value else NULL) 这样的方式,来保证不满足predicate的行不计入聚集结果中。

image.png

通用形态

将上面描述的内容泛化,WinMagic的通用形式如下:

image.png

  1. T1, T2, T3, T4是一组表(table/view)。
  2. 在内层,T1是内层非subsume的无关表,与T3(相关表)必须是lossless join。
  3. T2是外层无关表,不能与T4(相关表)直接join。这里应该是为了保证T3 Join T4这个内外层操作在数据上的一致性。

那么转换时,T4 pushdown 到子查询中计算window function,完成解相关+转为derived table,外层的T3 join T4就不需要了,这个derived table(WinMagic)与T2 join即可。

image.png

总结

WinMagic的本质是,外层已经包含了内层的相关表,所以可以直接复用下推后内层T3 join T4的结果来计算聚集,以window function的形式将聚集结果作为附加列加在结果上,然后外层基于这个结果再进行后续计算。

可以看到,这个变换还是非常简单的。目前PolarDB也实现了这个win magic的等价变换,基于完全相同的思路。

目录
相关文章
|
7月前
aggregate和annotate的区别
aggregate和annotate的区别。
37 1
|
SQL 分布式计算 Spark
SPARK Expand问题的解决(由count distinct、group sets、cube、rollup引起的)
SPARK Expand问题的解决(由count distinct、group sets、cube、rollup引起的)
754 0
SPARK Expand问题的解决(由count distinct、group sets、cube、rollup引起的)
|
SQL 关系型数据库 BI
PG:什么是grouping sets
PG:什么是grouping sets
132 0
1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'information_schema.PROFILING.SEQ' which is not functionally dependent on columns in GROUP BY clause
1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'information_schema.PROFILING.SEQ' which is not functionally dependent on columns in GROUP BY clause
210 0
1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'information_schema.PROFILING.SEQ' which is not functionally dependent on columns in GROUP BY clause
|
SQL 数据挖掘 关系型数据库
Hive 高阶--分组窗口函数--OLAP 相关分组函数(GROUPING SETS,CUBE,ROLLUP)|学习笔记
快速学习 Hive 高阶--分组窗口函数--OLAP 相关分组函数(GROUPING SETS,CUBE,ROLLUP)
254 0
Hive 高阶--分组窗口函数--OLAP 相关分组函数(GROUPING SETS,CUBE,ROLLUP)|学习笔记
|
SQL 关系型数据库 Linux
开发指南—DQL语句—Grouping Sets、Rollup和Cube扩展
在关系型数据库中,通常需要使用多个SELECT + UNION语句来实现按照多组维度的结果分组,PolarDB-X新增支持通过Grouping Sets、Rollup和Cube扩展来实现这一目的。此外,PolarDB-X还支持在SELECT命令或HAVING子句中使用GROUPING函数和GROUPING_ID函数,来帮助解释使用上述扩展时的结果。本文将介绍相关语法和示例。
146 0
|
SQL 关系型数据库 MySQL
Accelerating Queries with Group-By and Join By Groupjoin
这篇paper介绍了HyPer中引入的groupjoin算子,针对 join + group by这种query,可以在某些前提条件下,在join的过程中同时完成grouping+agg的计算。 比如用hash table来实现hash join和group by,就可以避免再创建一个hash table,尤其当join的数据量很大,产生的group结果又较少时,可以很好的提升执行效率。
359 0
Accelerating Queries with Group-By and Join By Groupjoin