性能优化案例课程 TPCH-Q2

select * from (
    select s_acctbal,s_name,n_name,p_partkey,p_mfgr,s_address,s_phone,s_comment
    from part,supplier,partsupp,nation,region
    where
        p_partkey = ps_partkey
        and s_suppkey = ps_suppkey
        and p_size = 25
        and p_type like '%COPPER'
        and s_nationkey = n_nationkey
        and n_regionkey = r_regionkey
        and r_name = 'ASIA'
        and ps_supplycost = (
            select
                min(ps_supplycost)
            from
                partsupp,
                supplier,
                nation,
                region
            where
                p_partkey = ps_partkey
                and s_suppkey = ps_suppkey
                and s_nationkey = n_nationkey
                and n_regionkey = r_regionkey
                and r_name = 'ASIA'
        )
        order by s_acctbal desc,n_name,s_name,p_partkey
    )
    where rownum <= 100;

where 条件中的子查询

select min(ps_supplycost) from…

涉及的表是 partsupp,supplier,nation,region。

外层 SQL 语句

select s_acctbal,s_name,…

相比多了 part 表。这就可以看出,子查询中的 p_partkey = ps_partkey 表示与外层语句关联的字段是 p_partkey。

为了更容易理解这个语句,可以新建一个视图 V:

select    *
from part,partsupp,supplier,nation,region
where
    p_partkey = ps_partkey
    and s_suppkey = ps_suppkey
    and s_nationkey = n_nationkey
    and n_regionkey = r_regionkey
    and r_name = 'ASIA'
    and p_size = 25
    and p_type like '%COPPER'

则原语句可以改写成:

select s_acctbal,s_name,n_name,p_partkey,p_mfgr,s_address,s_phone,s_comment
from V
where
    ps_supplycost = (
        select
            min(ps_supplycost)
        from
            V V1
        where
            V.p_partkey = V1.p_partkey
    ) 

这就把原查询变成了一个单表查询,相当于找出 V 中这样一些记录,使得这些记录的 ps_supplycost 值在所有与该记录的 partkey 值相同的记录中取值最小。

这个运算的本质是对 V 按 partkey 分组后对每组聚合,计算出每组中 ps_supplycost 最小的那些记录。但是,SQL 不支持这种聚合运算,于是只能写成子查询的情况。

1. 数据存储

这一题是遍历计算,大表 part、partsupp 外存成列式组表。Supplier 相对较大,也存成组表。

part、partsupp 两个表是主子表,要按照关联主键 partkey 排序。后续计算有较大结果集的分组,分组键恰好也是 partkey。那么数据按 partkey 有序存储时,在解决关联的同时,还能让分组运算的复杂度降低。

组表有一些索引信息,总会有一个最低的空间占用,不适合存储小数据表。因此小数据表 nation,region 采用行式的集文件,可以占用更少的磁盘空间,计算时全部读入。

对于较小的集文件和 supplier 组表,并没有特别的排序要求,就缺省都按照主键有序。

数据转换代码:


A

1

=file("region.tbl").import(; , "|").new(_1:R_REGIONKEY, _2:R_NAME, _3:R_COMMENT).sort(R_REGIONKEY)

2

=file("region.btx").export@b(A1)

3

=file("nation.tbl").import(; , "|").new(_1:N_NATIONKEY, _2:N_NAME, _3:N_REGIONKEY, _4:N_COMMENT).sort(N_NATIONKEY)

4

=file("nation.btx").export@b(A3)

5

=file("supplier.tbl").cursor(; , "|").new(_1:S_SUPPKEY, _2:S_NAME, _3:S_ADDRESS, _4:S_NATIONKEY, _5:S_PHONE, _6:S_ACCTBAL, _7:S_COMMENT).sortx(S_SUPPKEY)

6

=file("supplier.ctx").create@y(#S_SUPPKEY, S_NAME, S_ADDRESS, S_NATIONKEY, S_PHONE, S_ACCTBAL, S_COMMENT)

7

>A6.append(A5)

8

=file("part.tbl").cursor(; , "|").new(_1:P_PARTKEY, _2:P_NAME, _3:P_MFGR, _4:P_BRAND, _5:P_TYPE, _6:P_SIZE, _7:P_CONTAINER, _8:P_RETAILPRICE, _9:P_COMMENT).sortx(P_PARTKEY)

9

=file("part.ctx").create@y(#P_PARTKEY, P_NAME,P_MFGR, P_BRAND, P_TYPE, P_SIZE, P_CONTAINER, P_RETAILPRICE, P_COMMENT)

10

>A9.append(A8)

11

=file("partsupp.tbl").cursor(; , "|").new(_1:PS_PARTKEY, _2:PS_SUPPKEY, _3:PS_AVAILQTY, _4:PS_SUPPLYCOST, _5:PS_COMMENT).sortx(PS_PARTKEY,PS_SUPPKEY)

12

=file("partsupp.ctx").create@py(#PS_PARTKEY, #PS_SUPPKEY, PS_AVAILQTY,PS_SUPPLYCOST,PS_COMMENT)

13

>A12.append(A11)

子表 partsupp 必须按主表 part 匹配的方法分段存储,因此 A12 创建组表用了 @p,这样才能保证连接计算时的并行分段不会错位。

2. 关联解决

先解决表之间的关联,也就计算出前面说的视图 V。

小维表可以采用预先加载、预先关联的方式。

加载代码:


A

B

1

=file("region.btx").import@b().keys@i(R_REGIONKEY)

2

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

3

=file("supplier.ctx").open().import().keys@i(S_SUPPKEY)

4

=A2.switch(N_REGIONKEY,A1)

=A3.switch(S_NATIONKEY,A2)

5

>env(region,A1),env(nation,A2),env(supplier,A3)

A1 到 A3 预加载 region、nation、supplier 三个维表,建立带索引的主键。

A4、B4 实现维表间的预关联。

A5 将维表复制给全局变量,准备在后续计算中使用。

大表 part 和 partsupp 之间两个表是主子关系,采用有序归并的方式实现连接。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("part.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && pos@zh(P_TYPE,type))

4

=file("partsupp.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:supplier;A3)

5

=A4.joinx@im(PS_PARTKEY,A3:P_PARTKEY,P_MFGR)

6

=A5.select(PS_SUPPKEY.S_NATIONKEY.N_REGIONKEY.R_NAME==name)

7

for A6,10000



8

=interval@ms(A1,now())



A3 用前面题目讲到过的游标前过滤,在游标建立时使用过滤条件。

A4 把维表 supplier 连接到事实表 partsupp 上,PS_SUPPKEY:supplier 相当于 switch@i。

A5 对 part 和 partsupp 有序归并,把主表的字段拼接到子表上。

A7 中循环计算游标,并取数,计算时间。

这道题目完整的解法,是包括维表加载的。为了观察全部执行时间,后面的介绍就不采用预加载机制了。

包含维表加载的计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region.btx").import@b(R_REGIONKEY,R_NAME).keys@i(R_REGIONKEY)

4

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

5

=file("supplier.ctx").open().import(S_SUPPKEY,S_NAME,S_ADDRESS,S_NATIONKEY,S_PHONE,S_ACCTBAL,S_COMMENT).keys@i(S_SUPPKEY)

6

=A4.switch(N_REGIONKEY,A3)

=A5.switch(S_NATIONKEY,A4)

7

=file("part.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && pos@zh(P_TYPE,type))

8

=file("partsupp.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:A5;A7)

9

=A8.joinx@im(PS_PARTKEY,A7:P_PARTKEY,P_MFGR)

10

=A9.select(PS_SUPPKEY.S_NATIONKEY.N_REGIONKEY.R_NAME==name)

11

for A10,10000



12

=interval@ms(A1,now())



预加载的时候,结果存成全局变量,可能给多种计算使用,所以取出全部字段。这里 A3、A4、A5 只取出需要的字段。

不采用预加载的测试结果:

测试项目

执行时间(秒)

关联解决

12

3. 维表过滤

上节的方法是先连接维表再过滤,因为多条事实表记录会对应相同的维表记录,导致维表条件可能会被反复计算。如果先过滤维表,只剩下需要的记录,连接事实表的时候,一方面可以不必重复计算维表上的过滤条件,另一方面不满足条件的事实表记录直接在游标中过滤掉,能减少硬盘数据读取量和对象产生时间。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region.btx").import@b(R_REGIONKEY,R_NAME).keys@i(R_REGIONKEY)

4

=file("nation.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=A4.switch(N_REGIONKEY,A3)

6

=A4.select(N_REGIONKEY.R_NAME==name).derive@o().keys@i(N_NATIONKEY)

7

=file("supplier.ctx").open().cursor@m(S_SUPPKEY,S_NAME,S_ADDRESS,S_NATIONKEY,S_PHONE,S_ACCTBAL,S_COMMENT;S_NATIONKEY:A6).fetch().keys@i(S_SUPPKEY)

8

=file("part.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && pos@zh(P_TYPE,type))

9

=file("partsupp.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:A7;A8)

10

=A9.joinx@im(PS_PARTKEY,A8:P_PARTKEY,P_MFGR)

11

for A10,10000



12

=interval@ms(A1,now())



A6 对 supplier 的维表 nation 过滤。

A7 用过滤后的 nation 表连接、过滤 supplier 表,可以避免读取 supplier 中不合适的记录。

A9 用过滤后的 supplier 连接、过滤 partsupp 表,可以避免读取 partsupp 中不合适的记录。

测试结果:

测试项目

执行时间(秒)

关联解决

12

维表过滤

7

4. 维表序号化

如果维表的主键是从 1 开始的自然数 (也就是记录所在行号),那么就可以用键值直接按行号定位维表记录,而不再需要计算和比对 HASH 值了,从而加快与维表关联的速度。

直接用序号定位,维表不需要建立索引,占用内存也会小很多。

转换代码:


A

B

1

=file("region.btx").import@b()

=A1.new(#:R_REGIONKEY,R_NAME,R_COMMENT)

2

=file("region_2.btx").export@b(B1)

=file("nation.btx").import@b()

3

=B2.new(#:N_NATIONKEY, A1.pselect(R_REGIONKEY==N_REGIONKEY):N_REGIONKEY,N_NAME,N_COMMENT)

4

=file("nation_2.btx").export@b(A3)


5

=file("supplier.ctx").open().import().keys@i(S_SUPPKEY)

6

=A5.new(#:S_SUPPKEY,S_NAME,S_ADDRESS,B2.pselect(N_NATIONKEY==S_NATIONKEY):S_NATIONKEY,S_PHONE,S_ACCTBAL,S_COMMENT)

7

=file("supplier_2.ctx").create@y(#S_SUPPKEY, S_NAME, S_ADDRESS, S_NATIONKEY, S_PHONE, S_ACCTBAL, S_COMMENT)

8

>A7.append(A6.cursor())


9

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

=A9.cursor().id(P_TYPE).sort()

10

=file("p_type.btx").export@b(B9)

=A9.cursor().run(P_TYPE=B9.pos@b(P_TYPE))

11

=file("part_2.ctx").create@y(#P_PARTKEY, P_NAME,P_MFGR, P_BRAND, P_TYPE, P_SIZE, P_CONTAINER, P_RETAILPRICE, P_COMMENT)

12

>A11.append@i(B10.cursor())


13

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

=A13.run(PS_SUPPKEY=A5.pfind(PS_SUPPKEY))

14

=file("partsupp_2.ctx").create@py(#PS_PARTKEY, #PS_SUPPKEY,PS_AVAILQTY, PS_SUPPLYCOST,PS_COMMENT)

15

>A14.append(B13)


A1-A8,将维表 region、nation、supplier 的主键转为序号,相应的外键字段也转为序号。

B9、A10 将用 id 函数得到 P_TYPE 等的去重取值列表,再排序存入 btx 文件成为维表,完成字符串字段的序号化。

B10-A12 完成 part 表的外键序号化。

A12-A15 完成 partsupp 表的外键序号化。

计算代码:


A

B

1

=now()


2

>size=25

>type="COPPER"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

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

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

6

=A4.switch(N_REGIONKEY,A3:#)

=B5.switch(S_NATIONKEY,A4:#)

7

=file("part.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && pos@zh(P_TYPE,type))

8

=file("partsupp_2.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B6:#;A7)

9

=A8.joinx@im(PS_PARTKEY,A7:P_PARTKEY,P_MFGR)

10

=A9.select(PS_SUPPKEY.S_NATIONKEY.N_REGIONKEY.R_NAME==name)

11

for A10,10000


12

=interval@ms(A1,now())


A3、A4、A5 已经完成主键序号化的维表,加载时就不需再要建立索引了。

A8 中用维表主键的序号完成外键连接。

为了观察与“关联解决”计算的性能差别,这里还是采用先连接后过滤的方式。

测试结果:

测试项目

执行时间(秒)

关联解决

12

维表序号化

10

5. 对位序列

对于已经序号化的外键,我们还可以使用对位序列来处理维表上的过滤。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor@m(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

=file("partsupp_2.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#;A9)

11

=A10.joinx@im(PS_PARTKEY,A9:P_PARTKEY,P_MFGR)

12

for A11,10000



13

=interval@ms(A1,now())



A5 会产生一个与维表 p_type 同样长度的序列,其成员当满足过滤条件时为 p_type 的序号,不满足时为 null。part 表中的 P_TYPE 已经序号化处理过,它的值对应于 p_type 表中记录的行号,所以在 A9 就可以用 A5(P_TYPE) 来判断 part 中此行数据是否满足过滤条件。

B6、B7 也是对位序列,成员是当满足过滤条件时为原记录,不满足时为 null。

因为不需要做实质性的查找,对位序列的性能非常好,在处理维表过滤时很有效。

测试结果:

测试项目

执行时间(秒)

关联解决

12

维表过滤

7

维表序号化

10

对位序列

4

6. 聚合理解

到这里,关联解决已经处理完了。下面,要对关联结果分组,每组计算出 ps_supplycost 最小值所在的记录。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor@m(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

=file("partsupp_2.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#;A9)

11

=A10.joinx@im(PS_PARTKEY,A9:P_PARTKEY,P_MFGR)

12

=A11.groups(PS_PARTKEY;P_MFGR,minp@a(PS_SUPPLYCOST):m)

13

=A12.news(m;PS_PARTKEY,m.PS_SUPPKEY.S_ACCTBAL,m.PS_SUPPKEY.S_NAME,m.PS_SUPPKEY.S_NATIONKEY.N_NAME, P_MFGR,m.PS_SUPPKEY.S_ADDRESS, m.PS_SUPPKEY.S_PHONE,m. PS_SUPPKEY.S_COMMENT)

14

=A13.top(100;-S_ACCTBAL,N_NAME,S_NAME,PS_PARTKEY)

15

=interval@ms(A1,now())



A12 按照 PS_PARTKEY 分组,每组用聚合函数 minp 找出最小值所在的记录,而不是最小值本身。分组结果并不大,这里的 groups 函数直接用累积方式计算出结果序表。

A14 中利用 top 函数取出前 100 名,top 也是聚合函数,不需要全排序就可以得到前 100 名。

测试结果:

测试项目

执行时间(秒)

聚合理解

4.5

7. 有序分组

part 表和 partsupp 表有序归并的结果仍对主键有序,可以实现有序分组计算。有序分组可以返回游标,适用于结果集较大的情况。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor@m(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

=file("partsupp_2.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#;A9)

11

=A10.joinx@im(PS_PARTKEY,A9:P_PARTKEY,P_MFGR)

12

=A11.group@s(PS_PARTKEY;P_MFGR,minp@a(PS_SUPPLYCOST):m)

13

=A12.news(m;PS_PARTKEY,m.PS_SUPPKEY.S_ACCTBAL,m.PS_SUPPKEY.S_NAME,m.PS_SUPPKEY.S_NATIONKEY.N_NAME, P_MFGR,m.PS_SUPPKEY.S_ADDRESS, m.PS_SUPPKEY.S_PHONE,m. PS_SUPPKEY.S_COMMENT)

14

=A13.total(top(100;-S_ACCTBAL,N_NAME,S_NAME,PS_PARTKEY))

15

=interval@ms(A1,now())



A12 使用有序分组 group 函数计算出游标形式的结果集,有序分组只要比较相邻的记录就可以了,占用的内存更少,并能避免 Hash 计算及比对。@s 表示用累积方式聚合。

group@s 返回的是游标,当结果集比较大的时候也不会出现内存溢出的情况。

由于 A12 中的 group 函数得到的是游标,A4 中就要用游标的 total 函数,再加上聚合函数 top 计算出结果。

测试结果:

测试项目

执行时间(秒)

聚合理解

4.5

有序分组

4.5

因为数据实际上是按照分组字段有序的,所以累积分组计算时,有序分组效果不明显。

8. 有序分组 - 小结果集

在分组结果不大的时候,也可以用 groups@o 实现有序分组。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor@m(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

=file("partsupp_2.ctx").open().cursor(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#;A9)

11

=A10.joinx@im(PS_PARTKEY,A9:P_PARTKEY,P_MFGR)

12

=A11.groups@o(PS_PARTKEY;P_MFGR,minp@a(PS_SUPPLYCOST):m)

13

=A12.news(m;PS_PARTKEY,m.PS_SUPPKEY.S_ACCTBAL,m.PS_SUPPKEY.S_NAME,m.PS_SUPPKEY.S_NATIONKEY.N_NAME, P_MFGR,m.PS_SUPPKEY.S_ADDRESS, m.PS_SUPPKEY.S_PHONE,m. PS_SUPPKEY.S_COMMENT)

14

=A13.top(100;-S_ACCTBAL,N_NAME,S_NAME,PS_PARTKEY)

15

=interval@ms(A1,now())



A12 中用 groups@o,表示用有序分组的方法,只比较相邻的分组字段,直接计算出结果序表。

A14 中就用序表的 top 函数计算前 100 名了。

测试结果:

测试项目

执行时间(秒)

聚合理解

4.5

有序分组

4.5

有序分组 - 小结果集

4

9. 关联定位

part 表和 partsupp 表之间是主键关联的,用被过滤后的表的主键去查找关联表的记录,可以避免全表遍历,获得更好的性能。先看主表关联定位子表的情况。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor@m(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@m(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

=file("partsupp_2.ctx").open().news(A9,P_PARTKEY,P_MFGR,PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#)

11

=A10.groups@o(PS_PARTKEY;P_MFGR,minp@a(PS_SUPPLYCOST):m)

12

=A11.news(m;PS_PARTKEY,m.PS_SUPPKEY.S_ACCTBAL,m.PS_SUPPKEY.S_NAME,m.PS_SUPPKEY.S_NATIONKEY.N_NAME, P_MFGR,m.PS_SUPPKEY.S_ADDRESS, m.PS_SUPPKEY.S_PHONE,m. PS_SUPPKEY.S_COMMENT)

13

=A12.top(100;-S_ACCTBAL,N_NAME,S_NAME,PS_PARTKEY)

14

=interval@ms(A1,now())

A10 使用过滤后的主表 part 去取出关联的子表 partsupp 记录。因为主表对子表是一对多的关系,所以这里要使用 news,表示主表中取出的每条记录可能对应多条子表记录,此时会把主表字段按关联的子表记录数量复制,相当于做 joinx。

我们也可以用子表关联定位主表。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("partsupp_2.ctx").open().cursor@m(PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;PS_SUPPKEY:B7:#)

10

=file("part_2.ctx").open().new@r(A9,P_PARTKEY,P_MFGR,PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;P_SIZE==size && A5(P_TYPE))

11

=A10.groups@o(PS_PARTKEY;minp@a(PS_SUPPLYCOST):m)

12

=A11.news(m;PS_PARTKEY,m.PS_SUPPKEY.S_ACCTBAL,m.PS_SUPPKEY.S_NAME,m.PS_SUPPKEY.S_NATIONKEY.N_NAME, P_MFGR,m.PS_SUPPKEY.S_ADDRESS, m.PS_SUPPKEY.S_PHONE,m. PS_SUPPKEY.S_COMMENT)

13

=A12.top(100;-S_ACCTBAL,N_NAME,S_NAME,PS_PARTKEY)

14

=interval@ms(A1,now())

A10 中的 new 函数用过滤后的子表去取出关联的主表记录。@r 选项表示子表相同主键有多条记录时,复制主表的记录。

测试结果:

测试项目

执行时间(秒)

聚合理解

4.5

有序分组

4.5

有序分组 - 累计

4

关联定位 - 主表定位子表

3.5

关联定位 - 子表定位主表

4

主表过滤条件去掉的记录比较多,子表过滤条件去掉的比较少,所以主表过滤子表的关联定位,性能更好一些。

10. 列式计算

前面题目提到的列式计算,也可以用于本题。

计算代码:


A

B

C

1

=now()



2

>size=25

>type="COPPER"

>name="ASIA"

3

=file("region_2.btx").import@b(R_REGIONKEY,R_NAME)

4

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

5

=file("p_type.btx").import@b().(pos@zh(_1,type))

6

=A4.switch(N_REGIONKEY,A3:#)

=A6.(if(N_REGIONKEY.R_NAME==name,~,null))

7

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

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

8

=A7.cursor(;S_NATIONKEY:B6:#).fetch()

=A8.(B7(S_SUPPKEY)=~)

9

=file("part_2.ctx").open().cursor@mv(P_PARTKEY,P_MFGR;P_SIZE==size && A5(P_TYPE))

10

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

11

=A10.news(A9,P_PARTKEY,P_MFGR,PS_PARTKEY,PS_SUPPKEY,PS_SUPPLYCOST;B7(PS_SUPPKEY))

12

=A11.groups@o(PS_PARTKEY;minp@a(PS_SUPPLYCOST):m)

13

=A12.(m).conj().derive@o(B7(PS_SUPPKEY):s)

14

=A13.new(P_PARTKEY,s.S_ACCTBAL,s.S_NAME,s.S_NATIONKEY.N_NAME, P_MFGR,s.S_ADDRESS,s.S_PHONE,s.S_COMMENT)

15

=A14.top(100;-S_ACCTBAL,N_NAME,S_NAME,P_PARTKEY)

16

=interval@ms(A1,now())

在列式计算中,要尽量避免使用 switch 操作。A11 中使用 B7(PS_PARTKEY) 仅仅过滤数据,而不再使用 PS_SUPPKEY:B7:# 这种关联与过滤同时进行的方法。因为这里 PS_PARTKEY 是序号化的,可以直接在 supplier 表中通过行号获取记录而无需关联,可以更好地体现列式计算的优势。

A13 中先用 PS_SUPPKEY 在 supplier 表中找到对应的记录生成 s 列,然后再用 A14 中的 new 函数通过 s 列找到要显示的目标列数据。

测试结果:

测试项目

执行时间(秒)

聚合理解

4.5

有序分组

4.5

有序分组 - 累计

4

关联定位 - 子表关联主表

3.5

关联定位 - 主表关联子表

4

列式计算

2.5