性能优化案例课程 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