性能优化案例课程 TPCH-Q19

select
    sum(l_extendedprice * (1 - l_discount)) as revenue
from
    lineitem,part
where
    (
        p_partkey = l_partkey
        and p_brand = 'Brand#32'
        and p_container in ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')
        and l_quantity >= 7 and l_quantity <= 7 + 10
        and p_size between 1 and 5
        and l_shipmode in ('AIR', 'AIR REG')
        and l_shipinstruct = 'DELIVER IN PERSON'
    )
    or
    (
        p_partkey = l_partkey
        and p_brand = 'Brand#23'
        and p_container in ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')
        and l_quantity >= 18 and l_quantity <= 18 + 10
        and p_size between 1 and 10
        and l_shipmode in ('AIR', 'AIR REG')
        and l_shipinstruct = 'DELIVER IN PERSON'
    )
    or
    (
        p_partkey = l_partkey
        and p_brand = 'Brand#45'
        and p_container in ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')
        and l_quantity >= 22 and l_quantity <= 22 + 10
        and p_size between 1 and 15
        and l_shipmode in ('AIR', 'AIR REG')
        and l_shipinstruct = 'DELIVER IN PERSON'
);

这是两表连接的过滤查询汇总,结构上比较简单,但是过滤条件比较复杂。

1. 数据存储

lineitem 和 part 没有特殊的有序要求,按照主键有序存储。

继续使用题目 Q3 中的 lineitem.ctx 和 Q2 的 part.ctx,复制到本题的主目录中。

2. 一般实现

条件表达式中有不少项只与较小的维表 part 相关,而且这些项的计算复杂度较高(有 in 运算),如果先针对 part 表把这些项计算出来,则计算量将比针对两表连接结果集再计算要少得多,因为连接结果集是和 lineitem 表规模相同,要大得多。

计算代码:


A

B

1

=now()


2

>brand1="Brand#32"

>quantity1=7

3

>brand2="Brand#23"

>quantity2=18

4

>brand3="Brand#45"

>quantity3=22

5

=["SM CASE", "SM BOX", "SM PACK", "SM PKG"]

6

=["MED BAG", "MED BOX", "MED PKG", "MED PACK"]

7

=["LG CASE", "LG BOX", "LG PACK", "LG PKG"]

8

=[A5,A6,A7].conj().sort()

=["AIR","AIR REG"]

9

=file("part.ctx").open().cursor@m(P_PARTKEY,P_BRAND,P_SIZE,P_CONTAINER,0:FLAG;P_SIZE>=1 && P_SIZE<=15 && [brand1,brand2,brand3].contain(P_BRAND) && A8.contain(P_CONTAINER))

10

=A9.run(FLAG=if(P_BRAND==brand1 && A5.contain(P_CONTAINER) && P_SIZE<=5:1, P_BRAND==brand2 && A6.contain(P_CONTAINER) && P_SIZE<=10:2, P_BRAND==brand3 && A7.contain(P_CONTAINER) && P_SIZE<=15:3; 0)).select(FLAG>0).fetch().derive@o().keys@im(P_PARTKEY)

11

=file("lineitem.ctx").open().cursor@m(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE,L_DISCOUNT;L_PARTKEY:A10,L_SHIPINSTRUCT=="DELIVER IN PERSON" && B8.contain(L_SHIPMODE) && L_QUANTITY>= quantity1 && L_QUANTITY<=quantity3+10)

12

=A11.groups(;sum(if(case(L_PARTKEY.FLAG,1:L_QUANTITY<=quantity1+10,2:L_QUANTITY>=quantity2 && L_QUANTITY<=quantity2+10;L_QUANTITY>=quantity3),L_EXTENDEDPRICE*(1-L_DISCOUNT),0)))

13

=interval@ms(A1,now())


A9 将后续的条件合并,写到 part 的游标前过滤条件中,减少数据读取量。

A10 在 part 表中新产生一个 FLAG 字段,用于将 part 相关的复杂条件项先计算出来。当记录满足 SQL 中的 or 条件 1、2、3 时分别赋值为 1、2、3,都不满足则赋值 0,然后用 FLAG 值是否大于 0 来过滤。

在 A12 中再根据 FLAG 的值来选择用哪个 quantity 值对 L_QUANTITY 进行过滤。FLAG 相关的复杂计算式的计算次数,和 part 表的行数相同。

测试结果:

测试项目

执行时间(秒)

一般实现

5

3. 数据转换

本题要使用前面题目介绍的维表主键序号化方法,可以使用 Q8 中的 lineitem_4.ctx,复制到本题主目录中。

还要使用枚举型字符串数字化方法,part 中的 P_BRAND 和 P_CONTAINER 已分别在 Q16、Q17 中转换过了,复制到本题主目录。

还要在 lineitem_4 基础上转换 L_SHIPMODE、L_SHIPINSTRUCT 两个枚举型字符串字段。

转换代码如下:


A

1

=file("lineitem_4.ctx").open().cursor(L_SHIPINSTRUCT,L_SHIPMODE)

2

=A1.id(L_SHIPINSTRUCT,L_SHIPMODE)

3

=A2.(~.sort())

4

=file("l_shipinstruct.btx").export@b(A3(1))

5

=file("lineitem_4.ctx").open().cursor()

6

=A5.run(L_SHIPINSTRUCT=A3(1).pos@b(L_SHIPINSTRUCT),L_SHIPMODE=A3(2).pos@b(L_SHIPMODE))

7

=file("lineitem_19_3.ctx").create@py(#L_ORDERKEY,#L_LINENUMBER,L_PARTKEY, L_SUPPKEY, L_QUANTITY, L_EXTENDEDPRICE,L_DISCOUNT, L_TAX, L_RETURNFLAG, L_LINESTATUS, L_SHIPDATE, L_COMMITDATE, L_RECEIPTDATE,L_SHIPINSTRUCT, L_SHIPMODE, L_COMMENT)

8

>A7.append(A6)

转换后的计算代码:


A

B

1

=now()


2

=file("p_brand.btx").import@b().(_1)

3

=file("p_container.btx").import@b().(_1)

4

=file("l_shipinstruct.btx").import@b().(_1)

5

=file("l_shipmode.btx").import@b().(_1)

6

>brand1=A2.pos@b("Brand#32")

>quantity1=7

7

>brand2=A2.pos@b("Brand#23")

>quantity2=18

8

>brand3=A2.pos@b("Brand#45")

>quantity3=22

9

=A3.(["SM CASE", "SM BOX", "SM PACK", "SM PKG"].contain(~))

10

=A3.(["MED BAG", "MED BOX", "MED PKG", "MED PACK"].contain(~))

11

=A3.(["LG CASE", "LG BOX", "LG PACK", "LG PKG"].contain(~))

12

=A9.(~||A10(#)||A11(#))


13

=A5.(["AIR","AIR REG"].contain(~))

=A4.pos@b("DELIVER IN PERSON")

14

=file("part_17_3.ctx").open()

=A14.cursor@m().skip().(null)

15

=A14.cursor@m(P_PARTKEY,P_SIZE,P_BRAND,P_CONTAINER;P_SIZE>=1 && P_SIZE<=15 && [brand1,brand2,brand3].contain(P_BRAND) && A12(P_CONTAINER))

16

=A15.fetch().(B14(P_PARTKEY)=if(P_BRAND==brand1 && A9(P_CONTAINER) && P_SIZE>=1 && P_SIZE<=5:1, P_BRAND==brand2 && A10(P_CONTAINER) && P_SIZE<=10:2, P_BRAND==brand3 && A11(P_CONTAINER) && P_SIZE<=15:3; null))

17

=file("lineitem_19_3.ctx").open().cursor@m(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE,L_DISCOUNT;L_PARTKEY:B14:#,L_SHIPINSTRUCT==B13 && A13(L_SHIPMODE) && L_QUANTITY>= quantity1 && L_QUANTITY<=quantity3+10)

18

=A17.groups(;sum(if(case(L_PARTKEY,1:L_QUANTITY<=quantity1+10,2:L_QUANTITY>=quantity2 && L_QUANTITY<=quantity2+10,3:L_QUANTITY>=quantity3),L_EXTENDEDPRICE*(1-L_DISCOUNT),0)))

19

=interval@ms(A1,now())


A9、A10、A11、A13、B14 都是对位序列。

测试结果:

测试项目

执行时间(秒)

一般实现

5

数据转换

4

4. 列式计算

计算代码:


A

B

1

=now()


2

=file("p_brand.btx").import@b().(_1)

3

=file("p_container.btx").import@b().(_1)

4

=file("l_shipinstruct.btx").import@b().(_1)

5

=file("l_shipmode.btx").import@b().(_1)

6

>brand1=A2.pos@b("Brand#32")

>quantity1=7

7

>brand2=A2.pos@b("Brand#23")

>quantity2=18

8

>brand3=A2.pos@b("Brand#45")

>quantity3=22

9

=A3.(["SM CASE", "SM BOX", "SM PACK", "SM PKG"].contain(~))

10

=A3.(["MED BAG", "MED BOX", "MED PKG", "MED PACK"].contain(~))

11

=A3.(["LG CASE", "LG BOX", "LG PACK", "LG PKG"].contain(~))

12

=A9.(~||A10(#)||A11(#))


13

=A5.(["AIR","AIR REG"].contain(~))

=A4.pos@b("DELIVER IN PERSON")

14

=file("part_17_3.ctx").open()

=A14.cursor@m().skip().(null)

15

=A14.cursor@m(P_PARTKEY,P_SIZE,P_BRAND,P_CONTAINER;P_SIZE>=1 && P_SIZE<=15 && [brand1,brand2,brand3].contain(P_BRAND) && A12(P_CONTAINER))

16

=A15.fetch().(B14(P_PARTKEY)=if(P_BRAND==brand1 && A9(P_CONTAINER) && P_SIZE<=5:1, P_BRAND==brand2 && A10(P_CONTAINER) && P_SIZE<=10:2, P_BRAND==brand3 && A11(P_CONTAINER):3; null))

17

=file("lineitem_19_3.ctx").open().cursor@mv(L_PARTKEY,L_QUANTITY,L_EXTENDEDPRICE,L_DISCOUNT;B14(L_PARTKEY),L_SHIPINSTRUCT==B13 && A13(L_SHIPMODE) && L_QUANTITY>= quantity1 && L_QUANTITY<=quantity3+10)

18

=A17.groups(;sum(if(case(B14(L_PARTKEY),1:L_QUANTITY<=quantity1+10,2:L_QUANTITY>=quantity2 && L_QUANTITY<=quantity2+10,3:L_QUANTITY>=quantity3),L_EXTENDEDPRICE*(1-L_DISCOUNT),0)))

19

=interval@ms(A1,now())


列式计算不能使用 switch 给字段赋值,A17 用 B14(L_PARTKEY) 代替了 L_PARTKEY:B14:#,只实现过滤,没有赋值。A18 还要用 B14(L_PARTKEY) 再计算一次。

测试结果:

测试项目

执行时间(秒)

一般实现

5

数据转换

4

列式计算

2