【性能优化】5.1 [有序遍历] 有序分组汇总

 

【性能优化】4.9 [遍历技术] 列式计算

5.1 有序分组汇总

如果数据表对分组键有序,可以实施有序分组算法。

有序分组的过程很简单,遍历时只要将当前记录和最后一个分组子集对比键值,如果相同,则将这条记录继续分到这个分组子集中,如果不同,则这个分组子集就算计算完了,重新创建一个新的分组子集把这条记录放进去,如此反复这个直接遍历结束,就可以得到所有的分组子集。

类似地,对于大数据情况,更常见的是获得每个分组子集的汇总值。那么只要保持一个分组结果集,遍历到某条记录时,判断分组键值以决定将当前记录值累计到最后一条分组记录还是新产生一条分组记录。所有可累计的聚合运算(可用迭代函数描述的)都能用。

有序分组只需要进行相邻记录的比对,复杂度很低,也不存在哈希分组时的运气问题。这些分组子集或分组结果集中的记录,本身也是一个个计算出来的,遍历到的新数据,不会再改变已经计算完毕的分组子集和分组记录。这种计算过程不需要一片大内存来保持计算结果,很适合返回成游标的形式,所以也天然能适应大分组。

其实我们前面讲过大分组的排序分组,在第二轮就是用有序分组的算法。

比如,按时间有序的订单,可以用有序分组算法来计算每天的订单总额:


A

1

=file("orders.btx").cursor@b()

2

=A1.groups@o(dt;sum(amount))

SPL 中用 groups@o 表示有序分组,不过这里仍然是返回一个序表,使用 groups 都认为是小分组。

小分组的 groups@o 可以在多路游标上工作。对分组键值有序的数据表,可以看成由一个个连续的分组子集构成的,分段不一定正好在两个分组子集之间(可能在某个分组子集之内),但 groups@o 会在最后调整计算结果。

SPL 没有提供对应的 groupx@o,返回大分组的有序分组用了另一个函数:


A

1

=file("orders.btx").cursor@b()

2

=A1.group(dt).new(dt,~.sum(amount))

3

=A1.group(dt;sum(amount))

游标上的 group 函数会缺省认为游标对分组键值有序,它会返回一个游标,可以依次取出每个分组子集,然后可以在再附加进一步的运算。如果参数中写有聚合表达式,则会直接使用累计方法做完聚合,仍然返回游标,只是游标成员是有分组汇总值的分组记录。

这里 A2 和 A3 用不同的方式完成了同样结果的运算。A2 的方式中要取出每个分组子集,这要求分组子集不能太大;而 A3 的方式是会用迭代函数的计算过程完成汇总计算,碰到大分组子集也可以正常计算,仅要获得分组汇总值时,使用 A3 的代码更有优势。

A2 和 A3 都是游标,还需要 fetch 才能取出计算数据,而且 group 函数是延迟游标,实质的计算会在取数时才做。

大分组 group 不能直接支持多路游标,如果分段点落在某个分组子集之内,就会出现错误的运算结果。理论上,也可以仿照文本文件分段那样实现去头补尾策略,即从分段点(除第一段外)向后找到下一个分组键值不同的记录才开始这一段,而履历到本段结束时还要继续后下一段取一条记录并继续遍历到该分组子集结束。这样就能够保证用多路游标分段时也能计算出正确的结果。

但是,与文本文件不同的是,某个分组子集可能非常大,这有可能导致去头补尾策略中要去掉的“头”很大,这就会造成重复读取而导致性能下降。而且,在执行 group 函数时,已经不清楚游标是怎么计算出来的(游标可能有各种来源),去头补尾的动作要在数据表分段时处理好,这需要在分段时知道数据表对哪个字段是有序的。

SPL 的组表提供了另一种分段机制。因为 SPL 在创建组表时要事先指定数据结构,这样也就事先知道有序的字段等信息,之后在分段时就可以保证分段点一定落在分组子集之间。而文本文件和集文件不需要事先指定数据表结构,就没有提供这种机制了。


A

1

=file("orders.ctx").create@p(#dt,…)

1

=file("orders.ctx").open().cursor@m(dt,amount;;4)

2

=A2.group(dt;sum(amount))

在创建组表时使用 @p,通知 SPL 在分段时不要把第一字段(通常也是有序的字段)值相同的记录分到两个段去了。目前只提供了针对第一字段的分段处理,过去的实践表明,已经足够用了。

然后就可以使用多路游标来计算了,注意组表的多路游标语法和文本与集文件有点不同,要写在第二个分号之后的参数上。

【性能优化】5.2 [有序遍历] DISTINCT 和 COUNT(DISTINCT)
【性能优化】 前言及目录