SPL:TopN 和分组内 TopN
TopN 查询,即从数据中查找前 N 名 / 后 N 名。TopN 查询除了直接取值,还有查询 TopN 所在记录的详细信息,有时候还要查询 TopN 所在的行号位置。另外 TopN 查询还可能在分组中使用,用于查询组内的前 N 名 / 后 N 名。
1. TopN
取前 N 个 / 后 N 个,我们可以分为取值、取所在的行号以及取所在的记录,这三类需求来详细介绍。
以纳斯达克指数为例,部分数据如下:
Date | Open | Close | Amount |
2019/01/02 | 6506.910156 | 6665.939941 | 2261800000 |
2019/01/03 | 6584.77002 | 6463.5 | 2607290000 |
2019/01/04 | 6567.140137 | 6738.859863 | 2579550000 |
2019/01/07 | 6757.529785 | 6823.470215 | 2507550000 |
2019/01/08 | 6893.439941 | 6897.0 | 2380290000 |
… | … | … | … |
1.1 取前 N 个 / 后 N 个值
【例 1】 查询纳斯达克指数 2019 年成交量最高的 3 个量值。
SPL 脚本如下:
A | |
1 | =T("IXIC.txt") |
2 | =A1.select(year(Date)==2019) |
3 | =A2.top(-3, Amount) |
A1:导入纳斯达克指数数据
A2:选出 2019 年的数据。
A3:使用函数 A.top(n,x) 获取成交量最高的 3 个量值。其中 n 为正数时表示取前 N 个,n 为负数时表示取后 N 个。特殊的,当 n 为±1 时返回单值,类似于取最大值 / 最小值。
我们还可以查询纳斯达克指数 2019 年成交量最低的 4 个量值:
A | |
3 | =A2.top(4, Amount) |
A3:使用函数 A.top(n,x) 获取成交量最低的 4 个量值
1.2 取前 N 个 / 后 N 个所在的行号
在有序集合中,我们可以通过取前 N 个 / 后 N 个成员所在的行号,进行行间计算。
【例 2】 查询纳斯达克指数 2019 年收盘价最高的 3 天中,交易量相对前一日的涨幅。
SPL 脚本如下:
A | |
1 | =T("IXIC.txt") |
2 | =A1.select(year(Date)==2019).sort(Date) |
3 | =A2.ptop(-3, Close) |
4 | =A3.(A2(~).Amount/A2(~-1).Amount-1) |
A1:导入纳斯达克指数数据。
A2:选出 2019 年的数据,并按日期排序。
A3:使用函数 A.ptop(n,x) 取出最高的 3 个收盘价所在的行号。
A4:根据选出的行号,分别用当日交易量与前一日交易量相比来计算涨幅。
1.3 取前 N 个 / 后 N 个所在的记录
有些时候,我们并不关心前 N 个 / 后 N 个的值具体是多少,而是关心这些值所在的记录。比如查询期末考试数学前 3 名的学生姓名,2020 年销售额前 5 名的客户名称等等。
【例 3】 查询纳斯达克指数 2019 年收盘价最低的 5 个交易日的成交量。
SPL脚本如下:
A | |
1 | =T("IXIC.txt") |
2 | =A1.select(year(Date)==2019) |
3 | =A2.top(5; Close) |
4 | =A3.new(Date,Amount) |
A1:导入纳斯达克指数数据。
A2:选出 2019 年的数据。
A3:使用函数 A.top(n; x) 取出收盘价最低的 5 个交易日的记录。
A4:从 5 个交易日记录中取出日期和成交量。
2. 分组内 TopN
查询组内前 N 个 / 后 N 个是很常见的需求。例如查询每个班级数学成绩前两名是多少分,每个月单笔销售额前三名的客户是哪些等等。在本节中我们会分类介绍,如何解决在分组中使用 TopN 的问题。
2.1 分组后进行 TopN 运算
我们可以把 TopN 查询看作一种聚合运算。首先将数据按照一定的条件分组,然后再对每个分组后的结果集进行 TopN 查询。我们分别按照取值和取记录两种情况来讲解。
以学生成绩表为例,部分数据如下:
Class | StudentID | Subject | Score |
1 | 1 | English | 95 |
1 | 1 | Math | 90 |
1 | 1 | PE | 80 |
1 | 2 | English | 75 |
1 | 2 | Math | 84 |
… | … | … | … |
【例 4】 查询各班数学前两名的分数。
SPL脚本如下:
A | |
1 | =T("Score.txt") |
2 | =A1.select(Subject:"Math") |
3 | =A2.group(Class; ~.top(-2, Score):TOP2) |
4 | =A3.new(Class, TOP2(1):First, TOP2(2):Second) |
A1:导入成绩表数据。
A2:选出数学成绩。
A3:按班级分组,使用函数 A.top() 统计各班数学前两名的分数。
A4:创建结果表,第一列是班级,第二列是第一名,第三列是第二名。
【例 5】 查询各班每科成绩前三名的学生信息。
SPL脚本如下:
A | |
1 | =T("Score.txt") |
2 | =A1.group(Class,Subject;~.top(-3;Score):TOP3) |
3 | =A2.conj(TOP3) |
A1:导入成绩表数据。
A2:按班级和学科分组,并取出每组分数前三名的记录。
A3:将所有班级各科前两名所在记录合并。
2.2 以累计方式进行 TopN 运算
以累计方式进行 TopN 运算,不会产生分组的结果集,常用于数据量比较大的时候。我们还是按照取值和取记录两种情况来讲解。
以销售表为例,部分数据如下:
OrderID | Customer | OrderDate | SellerId | Amount |
81182311 | VINET | 2013/07/04 | 5 | 2440.0 |
98807954 | TOMSP | 2013/07/05 | 6 | 1863.4 |
65550721 | HANAR | 2013/07/08 | 4 | 1813.0 |
37311312 | VICTE | 2013/07/08 | 3 | 670.8 |
80138612 | SUPRD | 2013/07/09 | 4 | 3730.0 |
… | … | … | … | … |
【例 6】 查询 2014 年每个月销售额前两名的金额。
SPL脚本如下:
A | |
1 | =file("Sales.txt").cursor@t().select(year(OrderDate)==2014) |
2 | =A1.groups(month(OrderDate):Month; top(-2,Amount):TOP2) |
3 | =A2.news(TOP2;Month, ~:Amount) |
A1:产生销售表的游标,并选出 2014 年的数据。
A2:按月份分组,并取出每个月销售额前两名的金额。
A3:创建结果表,第一列是月份,第二列是金额。
【例 7】 查询 2014 年每个月销售额前三名的销售记录。
SPL脚本如下:
A | |
1 | =file("Sales.txt").cursor@t().select(year(OrderDate)==2014) |
2 | =A1.groups(month(OrderDate); top(-3;Amount):TOP3) |
3 | =A2.conj(TOP3) |
A1:产生销售表的游标,并选出 2014 年的数据。
A2:按月份分组,并取出每个月销售额前三名的记录。
A3:把每个月销售额前三名的记录合并。