浅析MySQL使用 GROUP BY 分组聚合与细分聚合
原创文章,转载请注明出处:http://www.cnblogs.com/weix-l/p/7521278.html;
若有错误,请评论指出,谢谢!
1. 聚合函数(Aggregate Function)
MySQL(5.7 ) 官方文档中给出的聚合函数列表(图片)如下:

详情点击https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html 。
除非另有说明,否则聚合函数都会忽略空值(NULL values)。
2. 聚合函数的使用
聚合函数通常对 GROUP BY 语句进行分组后的每个分组起作用,即,如果在查询语句中不使用 GROUP BY 对结果集分组,则聚合函数就对结果集的所有行起作用。为说明聚合函数的使用,现创建测试表 member 进行测试,member 的数据结构如下(使用 SELECT * FROM member 查询所得):

1)对结果集直接使用聚合函数
例如,使用聚合函数SUM () 计算所有会员(member) 的会费总和,则可使用:
SELECT SUM(fee) AS total_fee FROM member #计算所有会员会费总和
查询结果为:

SUM 函数会对全部字段列 fee 进行求和。当然,也可以求平均值、最大值等。
此外,也可以使用 WHERE 语句进行限定条件的聚合查询。例如,如果要查询 country 为 China 的会员会费之和,则为:
SELECT SUM(fee) AS China_total_fee, country FROM member WHERE country = 'China'
结果显示如下:

2)GROUP BY 对结果集分组后使用聚合函数——组内聚合
- GROUP BY 如何分组?
——将字段值相同的记录归为一组,可用COUNT(*) 统计组内成员个数;
- “组内聚合”为何意?
——以分组为单位,对组内每个成员使用聚合函数进行统计,即聚合函数是关于分组成员的函数。
试想,如果要从测试表中查询每个国家的会费总和呢?每个国家的会费,即先将所有结果集按 country 字段进行分组,country 值相同的行归为一组,然后以组为单位进行求和,这样查询的结果记录数等于分组字段不同值的个数。总共有来自三个国家(China, US, UK)的会员,所以分组聚合查询的结果记录数为3:
#查询每个国家的会费之和
SELECT SUM(fee) AS country_group_total_fee, country FROM member GROUP BY country
该查询语句会计算每个国家的会费之和,然后展示按每个国家分组的查询结果:

标准SQL( standard SQL) 和 MySQL 都提供 HAVING 语句对使用 GROUP BY 分组之后的结果进行条件筛选并产生新的结果集。例如,对于前述 1)中查询中国会员会费总和的问题,可以使用HAVING 语句:
#使用HAVING语句查询中国会员会费总和
SELECT SUM(fee) AS country_group_total_fee, country FROM member GROUP BY country HAVING country = 'China'
结果和上面一样:

这种方法与前述 1)中直接使用WHERE进行限定相比有些画蛇添足,为什么呢?因为 country 在此是分组字段(group column),对分组字段使用 HAVING 再次进行限定则就显得分组毫无意义,因为这时完全可以通过使用 WHERE 进行筛选后直接求和实现。那么,能使用非聚合列(nonaggregated column) 为限定条件吗?答案是,不仅没有意义,而且不允许。非聚合列指的是没有用聚合函数而是要查询的表本身的字段,因为使用 GROUP BY 分组查询后的聚合结果列中根本就不包含非聚合字段列,所以在解析SQL语句时根本找不到这个字段。比如,当你想获取每个国家性别为 man 的会员的会费之和时可能尝试在上面这个语句中使用 HAVING 对 sex 进行限定,像下面这样:
#错误:尝试使用HAVING 语句对非聚合字段进行限定
SELECT SUM(fee) AS country_group_total_fee FROM member m GROUP BY country HAVING m.sex = 'man'
执行后会报错 Err 1054:
[Err] 1054 - Unknown column 'm.sex' in 'having clause',提示未知的列m.sex,即使此处使用别名进行说明也不行。那么如何实现查询每个国家性别为 man 的会员的会费之和呢?当然还是使用WHERE 语句在 GROUP BY 进行分组之前就进行限定:
#在分组之前使用 WHERE 进行条件筛选
SELECT SUM(fee) AS country_group_total_fee, country FROM member WHERE sex = 'man' GROUP BY country
产生下面结果:
所以,HAVING 不能对分组本身起作用,但可以对分组后的结果进行查询限定,而限定的条件只能为聚合列(aggregated column),聚合列指的是在 SELECT 列 (SELECT list)中使用聚合函数产生的列,例如,此处的SUM(fee) 就是聚合列。在HAVING 中对聚合列进行限定,可以获取满足一定条件的聚合列结果。例如,在上面获取每个国家会员费用之和后再限定查询哪些会员费用之和超过10000,则可以使用下面的SQL 语句:
#查询会员费总和超过10000 的国家
SELECT SUM(fee) country FROM member GROUP BY country HAVING SUM(fee) > 10000
其结果就只剩下中国了:)

这是在标准SQL语句中的语法。在MySQL中扩展了HAVING 的用法,使其可以接受聚合列的别名作为限定条件,例如上面的要求使用别名的查询语句为:
#在HAVING 中使用别名
SELECT SUM(fee) AS country_group_total_fee, country FROM member GROUP BY country HAVING country_group_total_fee > 10000
其结果仍为:

3)GROUP BY 按多个分组字段分组后使用聚合函数——细分组内聚合
如果使用一个分组字段分组后的聚合结果记录数等于该分组字段不同值的个数,那么,使用多个分组字段以后呢?例如,在上面的查询的基础上,如果想要查询每个国家男、女分别的会费总和时,可以使用下面的语句:
#查询每个国家男、女会员的总和会费
SELECT SUM(fee) AS sex_and_country_group_total_fee, country, sex FROM member GROUP BY country,sex
结果如下:

从上面的结果可以看出来,“中国的男性会员出的总会费最多,而英国的男性会员的总会费最少”。总共三个国家,如果只按国家(country) 进行分组,只有三条记录,如果再按性别 (sex) 分,则会在分组后的每个组(也即每一行、每一条记录)里按性别的不同再进行细分,因为性别值只有两种,所以每个国家的分组又被分成两小组,则三个国家总共就有6小组(6 = 3 × 2),这样最终也就会有6条记录,如上图示。
为了解每个细分小组的个数,在SELECT 查询列的最后加上计算分组个数的聚合函数 COUNT(*):
#多分组字段分组,并统计每组个数
SELECT SUM(fee) AS sex_and_country_group_total_fee, country, sex, COUNT(*) AS row_num FROM member GROUP BY country, sex
结果如下:

上面的结果默认按靠近GROUP BY 的顺序进行排序,但如果要指定排序一句,则可使用ORDER BY ,例如,对上面的结果按 sex 排序:
#将分组结果按sex 排序
SELECT SUM(fee) AS sex_and_country_group_total_fee, country, sex, count(*) AS row_num FROM member GROUP BY country, sex ORDER BY sex
结果如下:

如果用其他字段对结果再进行细分呢?原理与上述两个字段进行分组时一样的,只是分组的深度越多,很明显结果的记录行数也越多,但不管怎样,你会发现每一条分组后的结果都是不一样的,这正是分组结果的特征,因为ORDER BY 本身就具有聚合功能,每个聚合列的结果是通过分组归类的结果,所以只有一条记录。
那么,如果用表的 主键 或 非空唯一性字段 进行分组,结果会怎样呢?比如,在本测试表中,id 是其主键,name 是非空的具有唯一性约束的字段,下面分别是以 id 和 name 进行分组的MySQL 语句和结果:
#以主键id进行分组
SELECT SUM(fee) AS sex_and_country_group_total_fee, id, COUNT(*) AS row_num FROM member GROUP BY id
结果如下:

#以非空唯一性约束字段进行分组
SELECT SUM(fee) AS sex_and_country_group_total_fee, name, COUNT(*) AS row_num FROM member GROUP BY name
结果如下:

很显然,这两种分组的结果中聚合函数结果列是一样的,每组的结果记录行数也一样,而且都为1,这说明按主键或非空唯一性约束字段进行分组其结果相同,且结果就是表的全部每一行记录。这样做可能没有太大意义,但有助于理解 GROUP BY 分组的原理。
3. 总结
1) 可直接对某个字段使用聚合函数,也可用 WHERE 语句筛选后对某个字段使用聚合函数;
2) 聚合函数通常作用于使用 GROUP BY 分组后的分组成员,用于统计每个分组的数据;
3) 不能对没有使用 GROUP BY 分组的聚合函数使用 HAVING 进行限定;
4) 可对使用 GROUP BY 分组查询后的结果使用 HAVING 进行限定,其限定条件最好为聚合函数列(本身或其他聚合函数);
5) 可在使用 GROUP BY 分组前使用 WHERE 对结果进行筛选,在分组后使用 HAVING 对聚合函数列进行限定;
6) 可使用 ORDER BY 对结果按照某个字段(任意字段或列,使用 GROUP BY 分组时也可使用聚合函数列)进行排序;
7) 当按照主键或非空唯一性约束字段进行分组时,其结果为整个表的全部记录。
4. 参考文献
[1]. MySQL 官方文档 URL: https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html
浅析MySQL使用 GROUP BY 分组聚合与细分聚合的更多相关文章
- 【mybatis】【mysql】mybatis查询mysql,group by分组查询报错:Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
mybatis查询mysql,group by分组查询报错:Expression #1 of SELECT list is not in GROUP BY clause and contains no ...
- mysql使用GROUP BY分组实现取前N条记录的方法
MySQL中GROUP BY分组取前N条记录实现 mysql分组,取记录 GROUP BY之后如何取每组的前两位下面我来讲述mysql中GROUP BY分组取前N条记录实现方法. 这是测试表(也不知道 ...
- MySQL数据库Group by分组之后再统计数目Count(*)与不分组直接统计数目的区别
简述问题“统计最新时刻处于某一状态的设备的数量” 1. 首先子查询结果,可以看到每个设备最新的状态信息 2.1 在子查询的基础上,对设备状态进行分组,进行统计每个状态的设备数量 2.1.1 可以看到处 ...
- MySQL之——GROUP BY分组取字段最大值
转载自:http://blog.csdn.net/l1028386804/article/details/54657412 假设有一个业务场景,需要查询用户登录记录信息,其中表结构如下: CREATE ...
- mysql中group by分组
为了测试group by语句,我们首先创建一个表: 然后向表内添加数据: 然后我们查看一下表的内容 接着我们分别按照性别和年龄对这个表进行分组; 我们可以看到表内的数据没有原表的多了,原因就是分组有去 ...
- mysql在group by分组后查询第二条/第三条乃至每组中任意一条数据
昨天老板让我查询项目中(众筹),没人刚发起感召后,前三笔钱的入账时间和金额,这把大哥整懵逼了,group by在某些方面是好使,但这次不能为我所用了,获取第一笔进账是简单,可以用group by 直接 ...
- mysql使用group by分组时出现错误ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and
问题: 1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ...
- mysql group by分组查询
分组的SQL语句有2个: group by 和分组聚合函数实现 partition by (oracle和postgreSQL中的语句)功能 group by + having 组合赛选数据 注意:h ...
- 浅析mongodb中group分组
这篇文章主要介绍了浅析mongodb中group分组的实现方法及示例,非常的简单实用,有需要的小伙伴可以参考下. group做的聚合有些复杂.先选定分组所依据的键,此后MongoDB就会将集合依据选定 ...
随机推荐
- Nginx正向代理设置
Nginx不仅可以做反向代理,实现负载均衡.还能用作正向代理来进行上网等功能. 正向代理:如果把局域网外的Internet想象成一个巨大的资源库,则局域网中的客户端要访问Internet,则需要通过代 ...
- MacOS Laravel 安装教程
一.到官网选择 Laravel 版本 根据个人的喜好选择安装的版本,我选择的是 5.8 https://laravel.com/docs/5.8/installation 以下是 Laravel 5. ...
- C语言 sscanf函数补充
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h&g ...
- docker卷挂载与容器内外互相拷贝数据
一.宿主机与容器的挂载 docker可以支持把一个宿主机上的目录挂载到镜像里.命令如下: docker run -it -v /mydownload:/download nginx:v1 /bin/b ...
- java开发异常Exception集锦
背景:整理开发过程中的异常问题 java.lang.Exception: No tests found matching 一般出现在新导入的工程中.在sts中通过open project的方式导入工程 ...
- 18 Ajax、Json以及jackson框架解析json的基本应用
1. Ajax (1)概念:ASynchronous JavaScript And XML 异步的JavaScript 和 XML 异步和同步:客户端和服务器端相互通信的基础上 * 客户端必须等待服务 ...
- laravel中一些非常常用的php artisan命令
php artisan 命令在开发laravel项目中非常常用,下面是一些总结 composer config -g repo.packagist composer https://mirrors.a ...
- flask框架(四)——flask CBV视图类解析
CBV视图类的两种基本写法 #第一种写法class IndexView(views.View): methods = ['GET'] decorators = [auth, ] def dispatc ...
- HTML常用技巧
1. 为网页链接添加快捷键:accesskey 属性 https://zhidao.baidu.com/question/2267343500557447508.html 2. 键盘事件设置快捷键:h ...
- MySQL数据库-表操作-SQL语句(一)
1. 数据库操作与存储引擎 1.1 数据库和数据库对象 数据库对象:存储,管理和使用数据的不同结构形式,如:表.视图.存储过程.函数.触发器.事件等. 数据库:存储数据库对象的容器. 数据库分两种 ...