性能优化案例课程 TPCH-Q17
select
sum(l_extendedprice) / 7.0 as avg_yearly
from
lineitem,part
where
p_partkey = l_partkey
and p_brand = 'Brand#33'
and p_container = 'LG DRUM'
and l_quantity < (
select
0.2 * avg(l_quantity)
from
lineitem
where
l_partkey = p_partkey
);
这种在子查询中用等值条件与主表关联的情况,都可以转换成 JOIN 来计算,相当于如下 SQL:
select
sum(l_extendedprice) / 7.0 as avg_yearly
from
lineitem,part,
(select
l_partkey lp,
0.2*avg(l_quantity) lq
from
lineitem
group by
l_partkey
) lpq
where
p_partkey = l_partkey
and p_brand = 'Brand#33'
and p_container = 'LG DRUM'
and l_partkey = lp
and l_quantity < lq;
子查询 lpq 是分组聚合运算,结果可以看成以 lp 为主键的表。这样,主查询就相当于是事实表 lineitem 关联两个维表 lpq 和 part 后的聚合运算。
1. 数据存储
lineitem、part 表没有什么特殊要求,按照主键有序存储。
继续使用题目 Q3 中的 lineitem.ctx 和 Q2 中的 part.ctx,复制到本题的主目录中。
2. 一般实现
计算代码:
A |
B |
|
1 |
=now() |
|
2 |
=brand="Brand#33" |
=container="LG DRUM" |
3 |
=file("part.ctx").open().cursor@m(P_PARTKEY;P_BRAND==brand && P_CONTAINER==container).fetch().keys@im(P_PARTKEY) |
|
4 |
=file("lineitem.ctx").open() |
|
5 |
=A4.cursor@m(L_PARTKEY,L_QUANTITY;A3.find(L_PARTKEY)) |
|
6 |
=A5.groups@u(L_PARTKEY;avg(L_QUANTITY):avg).run(avg/=5).keys@im(L_PARTKEY) |
|
7 |
=A4.cursor@m(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE;L_PARTKEY:A6) |
|
8 |
=A7.total(sum(if(L_QUANTITY<L_PARTKEY.avg,L_EXTENDEDPRICE,0)))/7.0 |
|
9 |
=interval@ms(A1,now()) |
A5、A7 采用了游标前过滤,过滤条件是主键查找。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
10 |
3. 数据转换
本题要使用维表主键序号化方法,Q8 中的 lineitem_4.ctx 和 Q16 中的 part_2.ctx 复制到本题的主目录。
在 Q16 的 part_2.ctx 已经完成了枚举型字符串字段 p_brand 的数字化,还要把枚举型字符串字段 p_container 数字化。
数据转换代码:
A |
|
1 |
=file("part_16_3.ctx").open().cursor().fetch() |
2 |
=A1.id(P_CONTAINER).sort() |
3 |
=file("p_container.btx").export@b(A2) |
4 |
=A1.run(P_CONTAINER=A2.pos@b(P_CONTAINER)) |
5 |
=file("part_17_3.ctx").create(#P_PARTKEY, P_NAME,P_MFGR, P_BRAND, P_TYPE, P_SIZE, P_CONTAINER, P_RETAILPRICE, P_COMMENT) |
6 |
>A5.append(A4.cursor()) |
计算代码:
A |
B |
|
1 |
=now() |
|
2 |
=file("p_brand.btx").import@b().(_1) |
|
3 |
=file("p_container.btx").import@b().(_1) |
|
4 |
=brand=A2.pos@b("Brand#33") |
=container=A3.pos@b("LG DRUM") |
5 |
=file("part_17_3.ctx").open() |
=A5.cursor().skip().(false) |
6 |
=A5.cursor@m(P_PARTKEY;P_BRAND==A4 && P_CONTAINER==B4).fetch().(B5(P_PARTKEY)=true) |
|
7 |
=file("lineitem_4.ctx").open() |
|
8 |
=A7.cursor@m(L_PARTKEY,L_QUANTITY;B5(L_PARTKEY)) |
|
9 |
=A8.groups@u(L_PARTKEY;avg(L_QUANTITY):avg).run(B5(L_PARTKEY)=avg*0.2) |
|
10 |
=A7.cursor@m(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE;L_PARTKEY:B5:#) |
|
11 |
=A10.total(sum(if(L_QUANTITY<L_PARTKEY,L_EXTENDEDPRICE,0)))/7.0 |
|
12 |
=interval@ms(A1,now()) |
A6 是对位序列,值是 true、false。
A9 中把对位序列 A6 的值更新为 false 或者 avg*0.2,这样在 A10 中,符合条件记录的 L_PARTKEY 会被赋值为 avg*0.2,就可以在 A11 中参加后续计算。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
10 |
数据变换 |
9 |
数据变换后提速不明显,是因为上一节的游标前过滤条件是主键查找计算,性能和本节使用的对位序列相差不是很大。
4. 列式计算
计算代码:
A |
B |
|
1 |
=now() |
|
2 |
=file("p_brand.btx").import@b().(_1) |
|
3 |
=file("p_container.btx").import@b().(_1) |
|
4 |
=brand=A2.pos@b("Brand#33") |
=container=A3.pos@b("LG DRUM") |
5 |
=file("part_17_3.ctx").open() |
=A5.cursor().skip().(false) |
6 |
=A5.cursor@mv(P_PARTKEY;P_BRAND==A4 && P_CONTAINER==B4).fetch().(B5(P_PARTKEY)=true) |
|
7 |
=file("lineitem_4.ctx").open() |
|
8 |
=A7.cursor@mv(L_PARTKEY,L_QUANTITY;B5(L_PARTKEY)) |
|
9 |
=A8.groups@u(L_PARTKEY;avg(L_QUANTITY):avg).run(B5(L_PARTKEY)=avg*0.2) |
|
10 |
=A7.cursor@m(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE;B5(L_PARTKEY)) |
|
11 |
=A10.total(sum(if(L_QUANTITY<B5(L_PARTKEY),L_EXTENDEDPRICE,0)))/7.0 |
|
12 |
=interval@ms(A1,now()) |
列式游标不能用 switch 给字段赋值,所以 A10 的过滤条件由上一节的 L_PARTKEY:B5:# 变为 B5(L_PARTKEY),只过滤不赋值,在 A11 中再用 B5(L_PARTKEY) 计算一次。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
10 |
数据转换 |
9 |
列式计算 |
6 |
英文版