一、 查询要求
Q4语句查询得到订单优先级统计值。计算给定的某三个月的订单的数量,在每个订单中至少有一行由顾客在它的提交日期之后收到。
Q4语句的特点是:带有分组、排序、聚集操作、子查询并存的单表查询操作。子查询是相关子查询。
二、 Oracle执行
Oracle编写的查询SQL语句如下:
select /*+ parallel(n) */
o_orderpriority,
count(*) as order_count
from
orders
where
o_orderdate >= date '1995-10-01'
and o_orderdate < date '1995-10-01' + interval '3' month
and exists (
select * from lineitem
where l_orderkey = o_orderkey and l_commitdate < l_receiptdate
)
group by
o_orderpriority
order by
o_orderpriority;
其中/*+ parallel(n) */ 是Oracle的并行查询语法,n是并行数。
脚本执行时间,单位:秒
并行数 | 1 | 2 | 4 | 8 | 12 |
Oracle | 504 | 311 | 270 | 189 | 207 |
三、 SPL优化
分析SQL语句,它有一个exists子查询,找出子表lineitem中有满足条件l_commitdate < l_receiptdate的记录,对应主表orders的记录。
exists子句都可以改写成join。事实上,SQL在实现时一般也是将exists转换成join来计算,否则,如果按exists的计算逻辑,又会导致N*M的复杂度(N和M分别是主子表的记录 数),这种性能完全不能接受。
将exists改写成join之后是这样:
select /*+ parallel(n) */
o_orderpriority,
count(*) as order_count
from (
select distinct l_orderkey,o_orderpriority from
orders,lineitem
where
o_orderdate >= date '1995-10-01'
and o_orderdate < date '1995-10-01' + interval '3' month
and l_orderkey = o_orderkey
and l_commitdate < l_receiptdate
)
group by
o_orderpriority
order by
o_orderpriority;
改写之后可以发现,这仍然是个主子表连接的问题,而且这里的连接运算是为了生成针对主表的某种过滤条件,中间结果集实际上是和orders表对应的,而一般主子连接的结果集是和子表对应的,所以还要对连接结果做针对orderkey的去重运算。
SPL一方面可以采用前面Q3所说的有序归并方法实现连接,另一方面还可以利用结果集的有序性(连接结果集对orderkey有序),快速实现去重运算。
SPL脚本如下:
A | |
1 | =1 |
2 | =now() |
3 | >date=date("1995-10-01") |
4 | =elapse@m(date,3) |
5 | =file(path+"orders.ctx").create().cursor@m(O_ORDERKEY,O_ORDERPRIORITY;O_ORDERDATE>=date && O_ORDERDATE < A4;A1) |
6 | =file(path+"lineitem.ctx").create().news(A5,L_ORDERKEY,L_COMMITDATE,L_RECEIPTDATE,O_ORDERPRIORITY; L_COMMITDATE < L_RECEIPTDATE) |
7 | =A6.group@1(L_ORDERKEY).groups(O_ORDERPRIORITY;count(1):order_count) |
8 | =now() |
9 | =interval@s(A2,A8) |
A5,A6使用了前面解释过的技巧。A7中的group@1即表示针对有序游标实现去重(仅分组后取第一条记录)。
脚本执行时间,单位:秒
并行数 | 1 | 2 | 4 | 8 | 12 |
Oracle | 504 | 311 | 270 | 189 | 207 |
SPL组表 | 160 | 81 | 41 | 24 | 20 |
并行性能仍然表现出色。
四、 进一步优化
上面分析过程说过,这里的连接运算本质上是为了过滤主表,我们还可以依照这样思路优化:把子表看成主表的集合字段,直接计算聚合值并用作过滤条件。这里可以把统计同一个L_ORDERKEY下满足条件L_COMMITDATE < L_RECEIPTDATE的记录数,如果等于0则将主表相应记录过滤掉,这样将大大减少了连接结果集的记录数,把分组去重的操作换成了效率更高的过滤操作,还能进一步提高性能。
SPL脚本如下:
A | |
1 | =1 |
2 | =now() |
3 | >date=date("1995-10-01") |
4 | =elapse@m(date,3) |
5 | =file(path+"orders.ctx").create().cursor@m(O_ORDERKEY,O_ORDERPRIORITY;O_ORDERDATE>=date && O_ORDERDATE < A4;A1) |
6 | =file(path+"lineitem.ctx").create().new(A5,O_ORDERPRIORITY,count():c;L_COMMITDATE < L_RECEIPTDATE).select(c>0) |
7 | =A6.groups(O_ORDERPRIORITY;count(1):order_count) |
8 | =now() |
9 | =interval@s(A2,A8) |
在A6的new函数中计算满足条件的子表记录数量,并过滤保留大于0的。
脚本执行时间,单位:秒
并行数 | 1 | 2 | 4 | 8 | 12 |
Oracle | 504 | 311 | 270 | 189 | 207 |
SPL组表 | 160 | 81 | 41 | 24 | 20 |
优化后 | 153 | 78 | 39 | 21 | 15 |
类似地,这个运算也很容易并行。