理解group by语义
个人认为sql中的group by和join是两大难点,因为它们转换了原来的表结构,group把表按某些字段统计缩小,join则使用笛卡尔积将多个表连接展开。
咱们回到group by,顾名思义group即为分组,即将原来的一整块数据分成几小块。分组是聚合的前提,聚合是在每个分组内进行一些统计,如在分组内的最大值,最小值,平均值,个数等。
未分组时查询返回的行直接与数据库表中的行对应,分组后分为多少组这个查询就会返回多少条记录,返回的记录中只能包含分组字段和在这个分组上的聚合操作的值(MySQL是例外见后面)
一般形式为:语法为:select t.sex, count(t.id) as cnt group by sex having cnt>2
查询中有三种字段:
查询字段:在select中出现的表中的字段
聚合字段:对表中的字段施加了聚合函数后形成的字段
分组字段:group by后的字段
无分组字段时
sql允许无分组字段即无group by直接在select用各聚合函数,这时表示整块数据为一个分组(单分组),聚合操作则是在整块全局上进行聚合,表示为本查询中所有记录的最大值,最小值,平均值,个数等
如果没有分组字段的聚合,则select后的字段只能全部都是带聚合的,不能直接是字段名否则Orcle会报错:ORA-00937: not a single-group group function(MySQL是个例外后面详述)
MySQL的坑
MySQL有一个让人不解的地方:在聚合查询中select可以出现非分组字段,语义上group by后的结果已经进行了分组变换,此时的数据均应对应于分组而不再是原表记录,在一个分组语义的记录里加入一个表的字段,语义上不太严谨,实际上MySQL会将非分组字段随便取一个值,不得不说算一个坑
比如下面的查询Oracle报错而MySQL不报错
select id, count(t.id) from
(
select 1 as id, 2 as age, ‘F’ as sex from dual union
select 2 as id, 2 as age, ‘M’ as sex from dual union
select 3 as id, 3 as age, ‘F’ as sex from dual union
select 4 as id, 4 as age, ‘M’ as sex from dual
)t
在MySQL中返回
1
4
该查询是单分组查询,4是总的记录条数没有问题,此时1是什么意思呢?它和计数有什么关系呢?
对null的处理
count(*)统计包含null在内的行数,count后面跟上字段名则表示统计这个字段非null的行数
聚合函数后面可以跟一个复合表达式,如多个字段相加,复合时如果其中一个字段为null则整个表达式为null,比如统计两个字段都非null的行数,可以count(f1+f2)
count,avg,sum后面跟上字段名时如果为null值将会忽略,比如计算平均值时null的那一行并不会记入总行数
如果带有group by则select后的字段要么是分组字段要么是聚合字段(MySQL仍然可以出现非分组字段)
关于having
与where不同的是having在group后进行进行过滤,where在group前进行过滤其写在group关键字前,having后过滤的字段可以是分组字段或聚合字段如:
select sex, count(*) as cnt having sex=’F’
select sex, count(*) as cnt having cnt>1
小技巧
有时需要测试sql语法但又觉得创建表麻烦,可以使用创建一个临时表,如下:
select id, count(t.id) from
(
select 1 as id, 2 as age, ‘F’ as sex from dual union
select 2 as id, 2 as age, ‘M’ as sex from dual union
select 3 as id, 3 as age, ‘F’ as sex from dual union
select 4 as id, 4 as age, ‘M’ as sex from dual
)t