【程序设计】11.3 [大数据] 有序游标

 

11.3 有序游标

我们来看游标上的 group 函数。

前面说过,对于游标,我们不能把 group 后的分组子集都保持在内存中继续计算,而放在外存中又会非常麻烦并严重影响性能,经常得不偿失。

不过,有一种情况的分组子集却可以放入内存中来处理。如果游标中的数据对分组键值是有序的(总是从小到大或者从大到小),并且每个分组子集都不大的时候,我们就可以在内存中总是保持一个分组子集,针对它完成相关的汇总运算后,当前这个分组子集就可以丢弃了,然后再读入下一个分组子集,如此反复。这样,我们不需要很大的内存也能完成一些必须依赖分组子集才能完成的运算了。

这就是游标上的 group 函数,它要求游标对分组键值是有序的。

而且,这个函数返回的是个延迟游标!


A

1

=file("data.txt").cursor@t()

2

=A1.group(dt)

3

=A2.fetch(2)

我们之前生成数据时保证了游标中数据对 dt 字段是有序的,现在查看 A3 的结果:

imagepng

这个 fetch 结果并不是个序表,而是有两个成员的序列(因为 fetch(2)),双击点开其中一个看:

imagepng

这个看起来像是个序表了,这是其中的某一组,从游标数据中取出来的记录构成的排列。其实也就是一个分组子集。

和序列不同,游标的 group 和 groups 并不对应,group 要求游标数据对分组键值有序,而且返回的延迟游标,并不当场计算;groups 则会立即遍历游标进行计算。

数据对于某个键值有序的游标,称为有序游标

利用这个分组子集,我们来计算每天中订单金额最多的那个地区的订单额和订单数,这要对分组子集再去分组汇总并取出最大值所在记录。利用我们以前学过的代码,这很容易:


A

1

=file("data.txt").cursor@t()

2

=A1.group(dt)

3

=A2.(~.groups(area; sum(amount):S,count(amount):C).maxp@a(S))

4

=A3.conj().fetch()

可以看出来,A.()这样的函数也可以针对游标使用,它也是个延迟游标。而且,基于延迟游标继续做下一轮计算时,可以像循环函数那样使用 ~ 表示当前成员,但是不能用 #也没有 [],因为游标的相对位置信息比较复杂,SPL 暂时没有实现,后面的数据还没读出,向后相邻引用也实现不了。

maxp@a 会返回一个序列(这里其实是个排列),所以 A3 实际上会是个相当于二层序列的游标,要再做一次 conj 才能变成单层的序列,conj 也返回的是延迟游标,还要做一次 fetch 才能得到结果,没有参数的 fetch 将取出所有的数据,我们知道数据只有 1000 天,最终结果集不会很大,可以全部取出来。

而且,从 group 本身和这里,我们也能看到,游标不一定总是读出序表,它可能 fetch 出二层序列。实际上,游标对应的是序列,并不总是序表,fetch 的结果也是个子序列,其成员可能是任何能成为序列成员的数据。

group 也有 @i 选项,和排列上的 group@i 同样被解释条件为 true 时分出新组,它也仍然返回延迟游标。另外,游标的 group(…;…) 函数也像内存排列那样被解释为 group(…).new(…),也会返回成延迟游标。

groups 也有 @o 和 @i 选项,可以执行有序分组提高运行性能,但 groups 总是立即计算的,即使针对有序游标也会立即遍历


A

1

=file("data.txt").cursor@t(dt,amount)

2

=A1.groups@o(month@y(dt);sum(amount):S,count(amount):C)

这个代码和没有 @o 是结果是一样的,但有了 @o 利用有序时性能更好。

和我们之前讲有序分组时类似,有序游标也可以用来协助日志解析,通常日志确实很大而无法读入内存,使用有序游标就显得更为必要。SPL 也扩展了 for 语句,使其直接支持从有序游标中每次取出一个分组子集。

和小数据情况类似,我们同样分三种情况来讨论:

1) 固定每 N 行对应一个事件:


A

B

1

=file("S.log").cursor@si()


2

=create()


3

for A1,3



>A2.insert()

注意,用游标读出文本时,要在 cursor 函数上加 @si 选项,这样它会读出字符串构成的序列,没有选项时会尽量解释成序表,但日志数据常常很乱,直接解析成序表很可能出错。

2) 行数不固定,每个事件前会有个固定起始字符串:

3

for A1.group@i(~=="---start---"),1

要加参数 1 表示每次读出一个分组子集,或者直接用 for 的扩展式:

3

for A1;~=="---start---":0

注意游标和后面的条件之间是分号。

3) 行数不固定,同一个事件的每行有个相同的前缀(比如该段日志所属的用户号等):

3

for A1.group(left(~,6)),1

每次读入 1 个分组子集,或者用 for 的扩展式

3

for A1;left(~,6))

读者可以对照前面使用有序分组处理小数据的代码。

【程序设计】 前言及目录

【程序设计】11.2 [大数据] 游标上的函数

【程序设计】11.4 [大数据] 大游标