性能优化案例课程 TPCH-Q7

select
    supp_nation,
    cust_nation,
    l_year,
    sum(volume) as revenue
from
    (
        select
            n1.n_name as supp_nation,
            n2.n_name as cust_nation,
            extract(year from l_shipdate) as l_year,
            l_extendedprice * (1 - l_discount) as volume
        from
            supplier,
            lineitem,
            orders,
            customer,
            nation n1,
            nation n2
        where
            s_suppkey = l_suppkey
            and o_orderkey = l_orderkey
            and c_custkey = o_custkey
            and s_nationkey = n1.n_nationkey
            and c_nationkey = n2.n_nationkey
            and (
                (n1.n_name = 'CHINA' and n2.n_name = 'RUSSIA')
                or (n1.n_name = 'RUSSIA' and n2.n_name = 'CHINA')
            )
            and l_shipdate between date '1995-01-01' and date '1996-12-31'
    ) shipping
group by
    supp_nation,
    cust_nation,
    l_year
order by
    supp_nation,
    cust_nation,
l_year;

这就是多表关联过滤后的分组聚合运算,其中包括主子表 orders 和 lineitem 关联。

1. 数据存储

大表 orders、lineitem 按照主键 orderkey 有序存储,连接的时候可以有序归并。其他表没有特殊的排序要求,就按照主键有序存储。

这样,就可以继续使用题目 Q3 中的 orders.ctx、lineitem.ctx、customer.ctx。

nation、supplier 表直接使用题目 Q2 的 nation.btx 和 supplier.ctx。

将这些表复制到本题的主目录中。

2. 一般实现

计算代码:


A

B

1

=now()


2

1995-01-01

1996-12-31

3

>name1="CHINA"

>name2="RUSSIA"

4

=file("nation.btx").import@b().keys@i(N_NATIONKEY)

5

>n1=A4.select@1(N_NAME==name1).N_NATIONKEY

6

>n2=A4.select@1(N_NAME==name2).N_NATIONKEY

7

=file("supplier.ctx").open().cursor@m(S_SUPPKEY,S_NATIONKEY;S_NATIONKEY==n1 || S_NATIONKEY==n2).fetch().keys@im(S_SUPPKEY)

8

=file("customer.ctx").open().cursor@m(C_CUSTKEY,C_NATIONKEY;C_NATIONKEY==n1 || C_NATIONKEY==n2).fetch().keys@im(C_CUSTKEY)

9

=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_CUSTKEY;O_CUSTKEY:A8)

10

=file("lineitem.ctx").open().news(A9,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,L_SHIPDATE,O_CUSTKEY;L_SHIPDATE>=A2 && L_SHIPDATE <=B2,L_SUPPKEY:A7)

11

=A10.select(O_CUSTKEY.C_NATIONKEY!=L_SUPPKEY.S_NATIONKEY)

12

=A11.groups(L_SUPPKEY.S_NATIONKEY:supp_nation,O_CUSTKEY.C_NATIONKEY:cust_nation,year(L_SHIPDATE):l_year; sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)): volume)

13

=A12.run(supp_nation=A4.find(supp_nation).N_NAME,cust_nation=A4.find(cust_nation).N_NAME)

14

=interval@ms(A1,now())

这里使用了前面题目中说过的游标建立时过滤、主子表有序关联和将关联字段转换成外键表指针的技巧,在 A12 中也是类似 Q5 中用整数键值代替字符串分组,在 A13 中再替换回来。

测试结果:

测试项目

执行时间(秒)

一般实现

13

3. 数据变换

数据变换要采用前面题目提到的维表主键序号化、日期整数化手段。

region、nation、supplier 表,可以直接使用以前的题目 Q2 转换的 region_2.btx,nation_2.btx 和 supplier_2.ctx。

customer 使用 Q5 中的 customer_3.ctx。

orders、lineitem 表使用 Q3 中的 orders_5.ctx、lineitem_5.ctx。

将这些表复制到本题的主目录中。

计算代码:


A

B

1

=now()


2

=days@o(date("1995-01-01"))

=days@o(date("1996-12-31"))

3

[CHINA,RUSSIA]


4

=file("nation_2.btx").import@b().(A3.pos(N_NAME))

5

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

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

6

=A5.cursor@m(S_SUPPKEY,S_NATIONKEY;A4(S_NATIONKEY)).fetch().(B5(S_SUPPKEY)=S_NATIONKEY)

7

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

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

8

=A7.cursor@m(C_CUSTKEY,C_NATIONKEY;A4(C_NATIONKEY)).fetch().(B7(C_CUSTKEY)=C_NATIONKEY)

9

=file("orders_5.ctx").open().cursor@m(O_ORDERKEY,O_CUSTKEY;O_CUSTKEY:B7:#)

10

=file("lineitem_5.ctx").open().news(A9,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,L_SHIPDATE,O_CUSTKEY;L_SHIPDATE>=A2 && L_SHIPDATE <=B2,L_SUPPKEY:B5:#)

11

=A10.select(O_CUSTKEY!=L_SUPPKEY).run(O_CUSTKEY=A4(O_CUSTKEY),L_SUPPKEY=A4(L_SUPPKEY))

12

=A11.groups@u(year(L_SHIPDATE)*9+L_SUPPKEY*3+O_CUSTKEY:gk; sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)): volume)

13

=A12.new(A3(gk%9\3):supp_nation,A3(gk%3):cust_nation,gk\9:l_year,volume).sort(supp_nation,cust_nation,l_year)

14

=interval@ms(A1,now())





A4、B5、B7 是对位序列。

A11 中把 O_CUSTKEY 和 L_SUPPKEY 替换成 A5 对位序列中的值,即 1 或 2,分别对应 CHINA 和 RUSSIA。A12 中使用 Q1 中介绍的分组键技巧,把按 3 个字段分组转变成按一个字段分组。

A12 中日期整数化以后仍然可以用 year 函数计算出它所属年份。

测试结果:

测试项目

执行时间(秒)

一般实现

13

数据变换

9

4. 列式计算


A

B

1

=now()


2

=days@o(date("1995-01-01"))

=days@o(date("1996-12-31"))

3

[CHINA,RUSSIA]


4

=file("nation_2.btx").import@b().(A3.pos(N_NAME))

5

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

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

6

=A5.cursor@m(S_SUPPKEY,S_NATIONKEY;A4(S_NATIONKEY)).fetch().(B5(S_SUPPKEY)=S_NATIONKEY)

7

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

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

8

=A7.cursor@m(C_CUSTKEY,C_NATIONKEY;A4(C_NATIONKEY)).fetch().(B7(C_CUSTKEY)=C_NATIONKEY)

9

=file("orders_5.ctx").open().cursor@mv(O_ORDERKEY,O_CUSTKEY;B7(O_CUSTKEY))

10

=file("lineitem_5.ctx").open().news(A9,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,L_SHIPDATE,O_CUSTKEY;L_SHIPDATE>=A2 && L_SHIPDATE<=B2 && B5(L_SUPPKEY))

11

=A10.derive@o(A4(B5(L_SUPPKEY)):s,A4(B7(O_CUSTKEY)):c).select@v(s!=c)

12

=A11.groups@u(year(L_SHIPDATE)*9+c*3+s:gk; sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):volume)

13

=A12.new(A3(gk%3):supp_nation,A3(gk%9\3):cust_nation,gk\9:l_year,volume).sort(supp_nation,cust_nation,l_year)

14

=interval@ms(A1,now())


在列式计算中,要尽量避免使用 switch 操作。A9 中使用 B7(O_CUSTKEY) 仅仅过滤数据,而不再使用 O_CUSTKEY:B7:# 这种关联与过滤同时进行的方法。

A11 中先用 O_CUSTKEY 在 B7 中的 customer 数据找到对应的 NATIONKEY,然后再取得 A4 的值。为了达到这个目的,在 B7 中计算的对位序列中,满足条件的是 NATIONKEY,不满足条件的是 null。

对于 lineitem 和 supplier 的关联,也是和 orders、customer 类似的处理。

测试结果:

测试项目

执行时间(秒)

一般实现

13

数据变换

9

列式计算

4