定位函数和定位计算

过滤是个很基本的运算,就是从一个集合中找出满足某些条件的成员构成的子集。SQL 中有 WHERE 和 HAVING 来实现过滤运算(严格地说,困为 SQL 缺乏离散性,WHERE 的运算结果是原数据表的子集的复制品)。SPL 也有 select 函数实现 SQL 中类似 WHERE 的运算(支持离散性的 SPL 返回的是真正的子集)。
对于无序集合而言,过滤运算找到满足条件的成员也就可以了,不必再有其它相关动作。但考虑到有序集合的时候,我们有时候还会关心这些满足条件的成员的位置。

比如按日期排序的某支股票的收盘价表,我们想知道用了几天股价涨到了 100 元以上。
用过滤运算找出股价超过 100 元的记录(也就是满足条件的成员)是没有意义的,我们事实上关心的是在有序集合中第一个使条件成立的成员的位置。
SQL 对付这种任务就比较麻烦,不能直接用过滤方法找出来,还是要造个序号,再计算出收盘价在 100 元以上的记录对应的序号的最小值。思路上有点“绕”了。

SELECT MIN(NO) 
FROM (SELECT ROWNUMBER() OVER ( ORDER BY TradeDate ) NO, ClosingPrice FROM Stock)
WHERE ClosingPrice>100

SPL 强化了对有序集合的支持,提供了一个 pselect 函数用于返回满足条件的成员的位置。

Stock.select( CloseingPrice > 100 )		// 收盘价大于100的记录
Stock.pselect@a( ClosingPirce > 100 )		// 收盘价大于100的记录的序号集合
Stock.pselect( ClosingPirce > 100 )		// 第一条收盘价大于100的记录的序号

刚才那个任务也就非常容易完成。

这种返回某些成员在有序集合中位置的函数,在 SPL 中称为定位函数,对应地,返回成员构成的子集的函数被称为选出函数,也就是过滤功能。而事实上,在 SPL 中,选出函数是被定位函数定义的,即先有 pselect 函数再有 select 函数,pselect 函数更为基础。SPL 中所有集合都是有序的,成员的位置是个非常基础的信息。
这些位置是有业务意义的,我们把刚才的运算需求改一下,想计算股价第一次涨到超过 100 元以上时的涨幅。

这个任务涉及有序计算和非常规聚合,用 SQL 实现就会“绕”得多:

WITH T AS (
    SELECT ROWNUMBER() OVER ( ORDER BY TradeDate ) NO, ClosingPrice,
           ClosingPrice - LAG(ClosingPrice) OVER ( ORDER BY TradeDate) Rising
    FROM Stock)
SELECT Rising FROM T WHERE NO = ( SELECT MIN(NO) FROM T WHERE ClosingPrice>100 )

这要把每天的涨幅都计算出来,CTE 语法生成的中间表也要被遍历两次。

如果集合有序,我们只要找到第一条收盘价大于 100 元的记录的位置,再和它的前一条的收盘价相减就可以了,这是很自然的思路。用 SPL 写出来就非常简单:

p=Stock.pselect( ClosingPrice > 100 )
return Stock(p).ClosingPrice - stock(p-1).ClosingPrice

这里第二句代码使用位置信息将收盘价从表中都取出,需要两次引用数据表。我们讲过,SPL 的 Lambda 语法可以很方便地实现相邻引用,但 Lambda 语法只能在循环集合的运算中使用,而不适用于这种针对一个确定位置计算的情况。
为此,SPL 提供了定位计算函数 calc,可以针对有序集合的指定位置使用 Lambda 语法:

p=Stock.pselect( ClosingPrice > 100 )
return Stock.calc(p,ClosingPrice - ClosingPrice[-1) )

这样的易读性更优。

前面我们讲过非常规聚合,可能返回数据表中最大最小值所在的记录,这也就是相当于返回了集合中的成员或子集(可能有多条记录都是最大最小值),一定程度也可以被视为选出函数,那么是不是也应该有对应的定位函数呢?
是的,SPL 中也有 pmax,pmin 函数用来返回有序集合中最大最小值所在的位置,而同样地,maxp,minp 函数也是基于 pmax,pmin 来定义的。
把刚才的问题改一下,我们想计算出股价最高那天的涨幅。利用这个定位函数就很容易写:

p=Stock.pmax( ClosingPrice )
return Stock.calc(p,ClosingPrice - ClosingPrice[-1) )

SQL 处理这个任务依然比较繁琐(假定最高股价是唯一的,不然还有更麻烦):

WITH T AS (
    SELECT ClosingPrice,
           ClosingPrice - LAG(ClosingPrice) OVER ( ORDER BY TradeDate) Rising
    FROM Stock)
SELECT Rising FROM T WHERE ClosingPrice = ( SELECT MAX(ClosingPrice) FROM T )

返回成员集合的 top 函数也可以理解为选出函数,也就会有对应的定位函数 ptop。
计算股价最高那三天的平均涨幅:

p=Stock.ptop( -3; ClosingPrice )
return Stock.calc(p,ClosingPrice - ClosingPrice[-1) ).avg()

这里就不写 SQL 了,大家自己了脑补。