MySQL GROUP BY子句的用法(非常详细)
分组查询是对数据按照某个或多个字段进行分组。MySQL 中使用 GROUP BY 关键字对数据进行分组,语法格式如下:
为了方便演示 GROUP BY 的用法,需要往数据表插入如下数据:
例如,要返回每个水果供应商提供的水果种类,就要在分组过程中用到 COUNT() 函数,把数据分为多个逻辑组,并对每个组进行集合计算。
【实例 1】根据 s_id 对表 fruits 中的数据进行分组,SQL 语句如下:
GROUP BY 子句按照 s_id 升序排序并对数据分组,可以看到 s_id 为 101、102、105 的供应商都提供 3 种水果,s_id 为 103、104、107 的供应商都提供 2 种水果,s_id 为 106 的供应商只提供 1 种水果。
如果要查看每个供应商提供的水果的种类,该怎么办呢?在 MySQL 中,可以在 GROUP BY 子句中使用 GROUP_CONCAT() 函数,将每个分组中的各个字段的值显示出来。
【实例】根据 s_id 对表 fruits 中的数据进行分组,将每个供应商提供的水果名称显示出来,SQL 语句如下:
【实例 2】根据 s_id 对表 fruits 中的数据进行分组,并显示水果种类大于 1 的分组信息,SQL 语句如下:
HAVING 关键字与 WHERE 关键字都是用来过滤数据的,两者有什么区别呢?最重要的区别是,HAVING 在数据分组之后进行过滤来选择分组,而 WHERE 在分组之前选择记录。另外,WHERE 排除的记录不再包括在分组中。
【实例 3】根据 s_id 对表 fruits 中的数据进行分组,并显示记录数量,SQL 语句如下:
MySQL 根据多字段的值来进行层次分组,分组层次从左到右,即先按第 1 个字段分组,然后在第 1 个字段值相同的记录中根据第 2 个字段的值进行分组,以此类推。
【实例 4】根据 s_id 和 f_name 字段对表 fruits 中的数据进行分组,SQL 语句如下:
为了演示效果,首先创建数据表,SQL 语句如下:
然后插入演示数据,SQL 语句如下:
【实例 5】查询订单价格大于 100 的订单号和总订单价格,SQL 语句如下:
接下来使用 ORDER BY 关键字按总订单价格升序显示结果,SQL 语句如下:
[GROUP BY 字段] [HAVING <条件表达式>]
- “字段”为进行分组时所依据的列名称;
- “HAVING <条件表达式>”指定显示满足表达式限定条件的结果。
为了方便演示 GROUP BY 的用法,需要往数据表插入如下数据:
mysql> INSERT INTO fruits (f_id, s_id, f_name, f_price) VALUES('a1', 101,'apple',5.2), ('b1',101,'blackberry', 10.2), ('bs1',102,'orange', 11.2), ('bs2',105,'melon',8.2), ('t1',102,'banana', 10.3), ('t2',102,'grape', 5.3), ('o2',103,'coconut', 9.2), ('c0',101,'cherry', 3.2), ('a2',103, 'apricot',2.2), ('l2',104,'lemon', 6.4), ('b2',104,'berry', 7.6), ('m1',106,'mango', 15.7), ('m2',105,'xbabay', 2.6), ('t4',107,'xbababa', 3.6), ('m3',105,'xxtt', 11.6), ('b5',107,'xxxx', 3.6);
MySQL创建分组
GROUP BY 关键字通常和集合函数一起使用,比如 MAX()、MIN()、COUNT()、SUM()、AVG()。例如,要返回每个水果供应商提供的水果种类,就要在分组过程中用到 COUNT() 函数,把数据分为多个逻辑组,并对每个组进行集合计算。
【实例 1】根据 s_id 对表 fruits 中的数据进行分组,SQL 语句如下:
SELECT s_id, COUNT(*) AS Total FROM fruits GROUP BY s_id;查询结果如下:
+------+-------+ | s_id | Total | +------+-------+ | 101 | 3 | | 103 | 2 | | 104 | 2 | | 107 | 2 | | 102 | 3 | | 105 | 3 | | 106 | 1 | +------+-------+
- s_id 表示供应商的 ID;
- Total 字段使用 COUNT() 函数计算得出。
GROUP BY 子句按照 s_id 升序排序并对数据分组,可以看到 s_id 为 101、102、105 的供应商都提供 3 种水果,s_id 为 103、104、107 的供应商都提供 2 种水果,s_id 为 106 的供应商只提供 1 种水果。
如果要查看每个供应商提供的水果的种类,该怎么办呢?在 MySQL 中,可以在 GROUP BY 子句中使用 GROUP_CONCAT() 函数,将每个分组中的各个字段的值显示出来。
【实例】根据 s_id 对表 fruits 中的数据进行分组,将每个供应商提供的水果名称显示出来,SQL 语句如下:
SELECT s_id, GROUP_CONCAT(f_name) AS Names FROM fruits GROUP BY s_id;查询结果如下:
+------+-------------------------+ | s_id | Names | +------+-------------------------+ | 101 | apple,blackberry,cherry | | 102 | orange,banana,grape | | 103 | apricot,coconut | | 104 | berry,lemon | | 105 | melon,xbabay,xxtt | | 106 | mango | | 107 | xxxx,xbababa | +------+-------------------------+由结果可以看到,GROUP_CONCAT() 函数将每个分组中的名称显示出来了,其名称的个数与 COUNT() 函数计算出来的相同。
MySQL使用HAVING过滤分组
GROUP BY 可以和 HAVING 一起限定显示记录所需满足的条件,只有满足条件的分组才会被显示。【实例 2】根据 s_id 对表 fruits 中的数据进行分组,并显示水果种类大于 1 的分组信息,SQL 语句如下:
SELECT s_id, GROUP_CONCAT(f_name) AS Names FROM fruits GROUP BY s_id HAVING COUNT(f_name) > 1;查询结果如下:
+------+-------------------------+ | s_id | Names | +------+-------------------------+ | 101 | apple,blackberry,cherry | | 102 | orange,banana,grape | | 103 | apricot,coconut | | 104 | berry,lemon | | 105 | melon,xbabay,xxtt | | 107 | xxxx,xbababa | +------+-------------------------+s_id 为 101、102、103、104、105、107 的供应商提供的水果种类大于 1,满足 HAVING 子句条件,因此出现在返回结果中;而 s_id 为 106 的供应商提供的水果种类等于 1,不满足限定条件,因此不在返回结果中。
HAVING 关键字与 WHERE 关键字都是用来过滤数据的,两者有什么区别呢?最重要的区别是,HAVING 在数据分组之后进行过滤来选择分组,而 WHERE 在分组之前选择记录。另外,WHERE 排除的记录不再包括在分组中。
MySQL在GROUP BY子句中使用WITH ROLLUP
使用 WITH ROLLUP 关键字之后,可以在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。【实例 3】根据 s_id 对表 fruits 中的数据进行分组,并显示记录数量,SQL 语句如下:
SELECT s_id, COUNT(*) AS Total FROM fruits GROUP BY s_id WITH ROLLUP;查询结果如下:
+------+-------+ | s_id | Total | +------+-------+ | 101 | 3 | | 102 | 3 | | 103 | 2 | | 104 | 2 | | 105 | 3 | | 106 | 1 | | 107 | 2 | | NULL | 16 | +------+-------+由结果可以看到,通过 GROUP BY 分组之后,在显示结果的最后面新添加了一行,该行 Total 列的值正好是上面所有数值之和。
MySQL多字段分组
使用 GROUP BY 可以对多个字段进行分组,GROUP BY 关键字后面跟需要分组的字段。MySQL 根据多字段的值来进行层次分组,分组层次从左到右,即先按第 1 个字段分组,然后在第 1 个字段值相同的记录中根据第 2 个字段的值进行分组,以此类推。
【实例 4】根据 s_id 和 f_name 字段对表 fruits 中的数据进行分组,SQL 语句如下:
mysql> SELECT * FROM fruits GROUP BY s_id,f_name;查询结果如下:
+-----+-----+------------+---------+ | f_id| s_id| f_name | f_price | +-----+-----+------------+---------+ | a1 | 101 | apple | 5.20 | | a2 | 103 | apricot | 2.20 | | b1 | 101 | blackberry | 10.20 | | b2 | 104 | berry | 7.60 | | b5 | 107 | xxxxx | 3.60 | | bs1 | 102 | orange | 11.20 | | bs2 | 105 | melon | 8.20 | | c0 | 101 | cherry | 3.20 | | l2 | 104 | lemon | 6.40 | | m1 | 106 | mango | 15.70 | | m2 | 105 | xbabay | 2.60 | | m3 | 105 | xxtt | 11.60 | | o3 | 103 | coconut | 9.20 | | t1 | 102 | banana | 10.30 | | t2 | 102 | grape | 5.30 | | t4 | 107 | xbababa | 3.60 | +-----+-----+------------+---------+由结果可以看到,查询记录先按照 s_id 进行分组,再对 f_name 字段按不同的取值进行分组。
注意,如果上面的例子报错为 SQL 模式问题,可以在 my.ini 文件中的 sql_mode 中删除 ONLY_FULL_GROP_BY,然后重启 MySQL 服务器即可解决问题。
MySQL GROUP BY和ORDER BY一起使用
某些情况下需要对分组进行排序。在前面的介绍中,ORDER BY 用来对查询的记录排序,它和 GROUP BY 一起使用就可以完成对分组的排序。为了演示效果,首先创建数据表,SQL 语句如下:
CREATE TABLE orderitems ( o_num int NOT NULL, o_item int NOT NULL, f_id char(10) NOT NULL, quantity int NOT NULL, item_price decimal(8,2) NOT NULL, PRIMARY KEY (o_num,o_item) ) ;
然后插入演示数据,SQL 语句如下:
INSERT INTO orderitems(o_num, o_item, f_id, quantity, item_price) VALUES(30001, 1, 'a1', 10, 5.2), (30001, 2, 'b2', 3, 7.6), (30001, 3, 'bs1', 5, 11.2), (30001, 4, 'bs2', 15, 9.2), (30002, 1, 'b3', 2, 20.0), (30003, 1, 'c0', 100, 10), (30004, 1, 'o2', 50, 2.50), (30005, 1, 'c0', 5, 10), (30005, 2, 'b1', 10, 8.99), (30005, 3, 'a2', 10, 2.2), (30005, 4, 'm1', 5, 14.99);
【实例 5】查询订单价格大于 100 的订单号和总订单价格,SQL 语句如下:
SELECT o_num, SUM(quantity * item_price) AS orderTotal FROM orderitems GROUP BY o_num HAVING SUM(quantity*item_price) >= 100;查询结果如下:
+-------+------------+ | o_num | orderTotal | +-------+------------+ | 30001 | 268.80 | | 30003 | 1000.00 | | 30004 | 125.00 | | 30005 | 236.85 | +-------+------------+从返回的结果中可以看到,orderTotal 列的总订单价格并没有按照一定顺序显示。
接下来使用 ORDER BY 关键字按总订单价格升序显示结果,SQL 语句如下:
SELECT o_num, SUM(quantity * item_price) AS orderTotal FROM orderitems GROUP BY o_num HAVING SUM(quantity*item_price) >= 100 ORDER BY orderTotal;查询结果如下:
+-------+------------+ | o_num | orderTotal | +-------+------------+ | 30004 | 125.00 | | 30005 | 236.85 | | 30001 | 268.80 | | 30003 | 1000.00 | +-------+------------+由结果可以看到,GROUP BY 子句按订单号对数据进行分组,SUM() 函数便可以返回总的订单价格,HAVING 子句对分组数据进行过滤,只返回总价格大于 100 的订单,最后使用 ORDER BY 子句升序输出。
注意,当使用 ROLLUP 时,不能同时使用 ORDER BY 子句进行排序,即 ROLLUP 和 ORDER BY 是互相排斥的。