SPL 分组(四)

分组

分组概念

人类认识世界,很多情况下是通过分类的方式来区别加以管理。在数据库中,分类表现为分组的方式。分组运算的实质,即将一个集合按某种规则拆分成若干子集。返回值应当是一个由集合构成的集合。用泛型来表达 List<List<T>>

分组是针对集合的分组,那么就会有分组键的选择,分组的划分方式,分组后的处理这几个关键步骤。

分组键

分组是针对集合的分组,那么分组可以采用字段,或字段计算的表达式,设置是与字段无关的数组或条件进行分组。用于分组的字段或表达式,为分组键,所以分组键可以是字段,也可以是计算的表达式。

分组划分方式

等值分组

将分组键相等的成员分到一个组,这种分组称为等值分组。等值分组要求原集合的所有成员都在且只在唯一的组中;满足等值划分的数学上称为完全划分。

不等值分组

比如分组中可能有空集存在,也可能有些原集合的成员不在任何一个组中,或者在多个组中。这种被称为不完全划分。

分组后集合的处理

分组之后,可能针对分组后的集合进行过滤,或者针对分组的子集进行处理。

分组的方式

分组运算的本意是将一个大集合按某种规则拆成若干个子集合,关系代数中没有数据类型能够表示集合的集合,于是强迫在分组后做聚合运算。

离散数据集中允许集合的集合,可以表示合理的分组运算结果,分组和分组后的聚合被拆分成相互独立的两步运算,这样可以针对分组子集再进行更复杂的运算。

关系代数中只有一种等值分组,即按分组键值划分集合,等值分组是个完全划分。
离散数据集认为任何拆分大集合的方法都是分组运算,除了常规的等值分组外,还提供了与有序性结合的有序分组,以及可能得到不完全划分结果的对位分组。)

把集合中具有相同属性的成员分配到同一个组,这就是分组运算

以集合中的某一列计算条件来进行划分整个集合。划分的方式可以非常灵活,下面就举例说明划分的多种方式

以选择列中不同元素的来进行划分整个集合

         划分的时候,默认将该列中的元素按照是否相同进行合并分组。如果元素不一样,就产生一个新组。这种划分方式与sql的groupby实现方式一致,这个最常用,最简单。

以选择列中元素变化来划分整个集合

         依次扫描整个序列,当分组键值和上一个成员的分组键相同时,则将该成员加入到当前的分组子集,如果分组键值发生变化了,则产生一个新的分组子集并加入当前成员,扫描完之后就得到一批分组子集,从而完成分组运算。

         工程实现的时候,group@o

以选择列中元素参与计算表达式来划分整个集合

//按出生年份分组;对birthday这一列,采用year函数计算之后进行划分

g1=employee.group(year(birthday))

以选择列中元素参与计算表达式为真来划分整个集合

         这时候分组方式是个逻辑表达式,每当计算出 true 时则产生一个新的分组子集。

@o 还有一个变种 @i,这时候分组键时是个逻辑表达式,每当计算出 true 时则产生一个新的分组子集。

我们回顾一下之前做过的计算股票最长连续上涨天数的问题。可以换一种思路,将交易数据按日期排序(经常本来就是有序的),然后依次扫描这些数据进行分组,如果某天价格比前一天上涨了,则将这一天继续和前一天分到同一组;如果没上涨,则产生一个新的分组。等扫描完之后会得到一些分组子集,在每个子集中股价都是连续上涨的,然后我们只要看哪个成员最多的子集就行了。

图形用户界面, 应用程序

描述已自动生成

        

外部输入的序列对选择列中元素进行划分。

先罗列出一个基准集合,然后将待分组集合成员的某个键值与基准集合成员比较,相同者则分到一个子集中,最后拆分出来的子集数量和基准集合成员数是相同的。这种分组SPL中称为对位分组

如我们要统计男女员工数量

SELECT gender,COUNT(*) FROM employee GROUP BY gender

希望返回的结果集有2条记录,并保证顺序。但如果公司员工全是男性或女性,这个运算结果就只有一行了,那可能就不是我们想要的了

SPL 中提供了对位分组运算,用来统计男女员工数量可以写成这样:

a=[Male,Female]                                         // 基准集合

g=employee.align(a,gender)                       // 函数align实现对位分组,拆分集合

g.new(a(#),~.len())                                    // 用分组子集计算汇总

这种对位分组在日常统计中是很常见的,比如按地区、按部门统计,都可以事先把基准集合列出来,而且我们经常还要求结果集必须按基准集合的次序出现,等值分组就不能保证这个次序,还要再排序,而排序时还是要提供这个基准集合,因为原集合成员属性中没有这个信息。

对位分组可能出现空子集,它也不能保证任何原集合的成员都被拆到某个子集中(比如有些不重要的成员没有被列入基准集合),不过对位分组能保证每个成员最多只出现在一个子集中。

外部输入的表达式对选择列中元素进行划分

把对位分组推广成更一般的枚举分组
枚举分组是指,事先指定一组条件,将待分组集合的成员作为参数计算这批条件,条件成立者都被划分到与该条件对应的一个子集中,分组结果中的子集和事先指定的条件一一对应。
比如,将员工按年龄段分组统计人数,SPL 中可以这样写:

a=[?<=30,?<=40,?>40]                     // 用?表示要代入的参数

g=employee.enum(a,age)                           // 设计函数enum实现枚举分组,拆分集合

....

显然,枚举分组在日常业务中也是不少见的。

枚举分组和对位分组很象,都需要先列出一个基准集合,事实上,对位分组就是一种特殊的枚举分组。不过,不同的是,枚举分组可能制造出有重复成员的子集,也就是可重分组

a=[?<=30,?>20 && ?<=40,?>50]       // 条件有重叠

g=employee.enum@r(a,age)

可重分组在实际业务中相对罕见一些,不过了解一下也有助于再次理解分组运算的实质。

表面上看,对位分组和枚举分组和 SQL 的 GROUP BY 差别很大,但理解了分组运算的本质后,就会明白它们其实是一回事:把某个集合拆分成若干子集。只是拆分的方法各有不同。

图形用户界面, 应用程序, 表格

描述已自动生成

分组键为表达式,age(BIRTHDAY)

分组方式采用对位的方式

从这个可以看出enum与Group的分组方式不同点

Group,是针对本表中的某列或对某列使用表达式来,针对默认的本列的计算值进行比较计算分组条件

Enum,是针对本表中的某列或对某列使用表达式,然后在对应其他序列进行比较分组条件

分组后运算

         分组运算的实质是将一个集合按照某种规则拆分成若干个子集,也就是说,返回值应当是一个由集合构成的集合。对分拆的结果,需要进行进一步计算。在Java中,分组会出现Key,value的结构。在SPL中,分组之后是List<List<T>>的结构。即集合分组之后,也是小的集合,不是Key,Value的结构。

对分组进行过滤

例1           根据分组键进行过滤

                   根据子集中的特性进行过滤,类似sql中having

图形用户界面, 应用程序, Teams

描述已自动生成

例2:  根据子集的条件来筛选分组

想找出公司里有哪些员工和其他员工会在同一天过生日,很简单的思路是将员工按生日分组,然后找出成员数大于 1 的分组子集,再做个并集。这时候就不是只对聚合值(分组子集的成员数)感兴趣,而是对分组子集本身进行合并计算。

                   SQL的表达比较啰嗦,需要用子查询,并且要遍历两次原集合

SELECT * FROM employee WHERE birthday IN

    ( SELECT birthday FROM employee GROUP BY birthday HAVING COUNT(*)>1 )

采用spl的表达式如下

=T("Employee.csv")

=A1.group(BIRTHDAY)    

=A2.select(~.len()>1).conj()    

//SPL code from SPL:获取分组子集后再统计 - 乾学院

A1:导入员工表。

A2:使用了函数 A.group() 按出生日期分组。

A3:选择成员数量大于 1 的分组,即有相同出生日期的子集。再将这些子集合并。

 严格来说,分组和汇总是两个独立的动作,但在 SQL 中总是一起出现,从而给人一种两者必须同时使用的假象。事实上,这种组合是对分组操作的一种局限,或者说分组之后,能够进行的计算远不止 SQL 中的几种聚合函数。

分组之后,以分组子集的统计值来筛选分组

图形用户界面, 文本, 应用程序

描述已自动生成

注:A1.group的含义是,针对A1的集合进行group操作

对子集进行聚合计算

               类似sqlgroup by 前的select sum/count5大计算

SQL中,有 GROUP BY 子句时,SELECT 部分除了分组字段外,就只能写入聚合运算表达式了。聚合表达式有5个,sumcountmaxminavg

由于 SQL 的离散性问题,无法返回集合的集合,只能强迫实施聚合运算了,在group的时候,必须要同时返回聚合值。

有时候,需要对这些分组子集而不是聚合值更感兴趣。在需要采用Java等高级语言来进行处理。

标准 SQL 中提供了五种最常用的聚合运算:SUM/COUNT/AVG/MIN/MAX。观察这几个运算,我们发现它们都可以看成是一个以集合为参数返回单值的函数,我们就先把这个共同点理解为聚合运算的定义,把集合变成单值,多个值变成一个值,也就是发生了 " 聚合“,所以叫聚合运算。

SPL中的处理方式

     

对子集进行其他计算

有时候我们关心的不是结果数值本身,而是与结果数值相关的信息。

         例一:从日志表中找出某个用户第一次登录时用的 IP 地址,而不是登录时刻。

标准 SQL 写这个运算大概是这样:

SELECT ip_address FROM  LogTable WHERE user=? AND logintime=

    (SELECT MIN(logintime) FROM LogTable WHERE user=?)

用子查询先计算出该用户的第一次登录的时刻,再查找出该时刻时用到的 IP 地址,这要把数据集遍历两次。

      采用spl之后

LogTable.group(user).(~.minp(logintime))

在SQL中无法处理,因为分组与计算是关联在一起。

例二:根据分组子集的结果来进一步处理分组子集

这个时候,分组不变

  

 如top,或者满足条件的count

=LogTable.groups(user;(a=~.top(logintime,-2),a(2)-a(1)))  //每个用户最后的两次登录时间间隔

对分组子集的动作,可以分为sql的5种聚合,sql以外的其他聚合,也可以是非聚合的方式(如选择子集的部分内容)。总之是对子集进行的动作。

【例 2】查询年龄低于部门平均年龄的员工。

   前面已经介绍过,在 SPL 中函数 A.group()用于分组。我们可以在函数 A.group() 中,定义在分组后对每个分组子集的运算。不限于 SUM、COUNT 等聚合运算,可以定义一些复杂运算。

SPL脚本如下:

=T("Employee.csv")

=A1.group(DEPT; (a=~.avg(age(BIRTHDAY)), ~.select(age(BIRTHDAY)<a)):YOUNG)       

=A2.conj(YOUNG)   

//SPL code from SPL:获取分组子集后再统计 - 乾学院

A1:导入员工表。

A2:按部门分组,并在每个分组中选出年龄低于平均年龄的记录。在函数 A.group() 的聚合运算中,我们可以使用临时变量,使得运算更加简单易懂。

A3:将选出的记录合并。

例三将该数据集按照任务编号进行分组,然后每个子集中选择version最大的记录

分组之后的合并.conj();找一个例子

Group 之后获取子集,然后进行进一步计算

合并的时候,一定要采用new的方式,获取到所有的成员,参与后续的合并。

采用conj,可以得到一个正常的表。但是它还是一个key,value

然后进行和合并的时候,就是只有一个成员。

分组计算中的符号

每个函数有它作用的对象。A 是个集合,A.select 当然只会过滤 A(的成员),它就不知道 A 的成员是不是集合。

要过滤 A 的成员子集,那就对着这些子集去做过滤。A.(~.select(…) ),这才是明确知道 A 的成员是集合,并再使用过滤动作。

特别注意:A.select 与A.(~.select(..))

1、对分组后的子集进行过滤。以这个过滤条件来筛选 分组。

   实现方式,采用分组后的,使用 A2.select()或A2.count(),类似sql中的having。

2、对分组后的子集进行过滤,以这个过滤条件  仅仅筛选 分组子集,但是分组不变。

  A2。

  比如:在一个雇员的表中,group 城市。然后针对每个城市中的人员根据 select

注:

去重可以用A.group@1 (x1,…),如果是按所有字段去重则可以写成A.group@1 (~.array())
top(-2) 如果本组只有 1 条则结果就只有一条。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值