Mysql高手系列 - 第9篇:詳解分組查詢,mysql分組有大坑!


這是Mysql系列第9篇。

環境:mysql5.7.25,cmd命令中進行演示。

本篇內容

  1. 分組查詢語法
  2. 聚合函數
  3. 單字段分組
  4. 多字段分組
  5. 分組前篩選數據
  6. 分組后篩選數據
  7. where和having的區別
  8. 分組后排序
  9. where & group by & having & order by & limit 一起協作
  10. mysql分組中的坑
  11. in多列查詢的使用

分組查詢

語法:

SELECT column, group_function,... FROM table
[WHERE condition]
GROUP BY group_by_expression
[HAVING group_condition];

說明:

group_function:聚合函數。

group_by_expression:分組表達式,多個之間用逗號隔開。

group_condition:分組之后對數據進行過濾。

分組中,select后面只能有兩種類型的列:

  1. 出現在group by后的列
  2. 或者使用聚合函數的列

聚合函數

函數名稱 作用
max 查詢指定列的最大值
min 查詢指定列的最小值
count 統計查詢結果的行數
sum 求和,返回指定列的總和
avg 求平均值,返回指定列數據的平均值

分組時,可以使用使用上面的聚合函數。

准備數據

drop table if exists t_order;

-- 創建訂單表
create table t_order(
  id int not null AUTO_INCREMENT COMMENT '訂單id',
  user_id bigint not null comment '下單人id',
  user_name varchar(16) not null default '' comment '用戶名',
  price decimal(10,2) not null default 0 comment '訂單金額',
  the_year SMALLINT not null comment '訂單創建年份',
  PRIMARY KEY (id)
) comment '訂單表';

-- 插入數據
insert into t_order(user_id,user_name,price,the_year) values
  (1001,'路人甲Java',11.11,'2017'),
  (1001,'路人甲Java',22.22,'2018'),
  (1001,'路人甲Java',88.88,'2018'),
  (1002,'劉德華',33.33,'2018'),
  (1002,'劉德華',12.22,'2018'),
  (1002,'劉德華',16.66,'2018'),
  (1002,'劉德華',44.44,'2019'),
  (1003,'張學友',55.55,'2018'),
  (1003,'張學友',66.66,'2019');
mysql> select * from t_order;
+----+---------+---------------+-------+----------+
| id | user_id | user_name     | price | the_year |
+----+---------+---------------+-------+----------+
|  1 |    1001 | 路人甲Java    | 11.11 |     2017 |
|  2 |    1001 | 路人甲Java    | 22.22 |     2018 |
|  3 |    1001 | 路人甲Java    | 88.88 |     2018 |
|  4 |    1002 | 劉德華        | 33.33 |     2018 |
|  5 |    1002 | 劉德華        | 12.22 |     2018 |
|  6 |    1002 | 劉德華        | 16.66 |     2018 |
|  7 |    1002 | 劉德華        | 44.44 |     2019 |
|  8 |    1003 | 張學友        | 55.55 |     2018 |
|  9 |    1003 | 張學友        | 66.66 |     2019 |
+----+---------+---------------+-------+----------+
9 rows in set (0.00 sec)

單字段分組

需求:查詢每個用戶下單數量,輸出:用戶id、下單數量,如下:

mysql> SELECT 
            user_id 用戶id, COUNT(id) 下單數量
        FROM
            t_order
        GROUP BY user_id;
+----------+--------------+
| 用戶id   | 下單數量     |
+----------+--------------+
|     1001 |            3 |
|     1002 |            4 |
|     1003 |            2 |
+----------+--------------+
3 rows in set (0.00 sec)

多字段分組

需求:查詢每個用戶每年下單數量,輸出字段:用戶id、年份、下單數量,如下:

mysql> SELECT 
            user_id 用戶id, the_year 年份, COUNT(id) 下單數量
        FROM
            t_order
        GROUP BY user_id , the_year;
+----------+--------+--------------+
| 用戶id   | 年份   | 下單數量     |
+----------+--------+--------------+
|     1001 |   2017 |            1 |
|     1001 |   2018 |            2 |
|     1002 |   2018 |            3 |
|     1002 |   2019 |            1 |
|     1003 |   2018 |            1 |
|     1003 |   2019 |            1 |
+----------+--------+--------------+
6 rows in set (0.00 sec)

分組前篩選數據

分組前對數據進行篩選,使用where關鍵字

需求:需要查詢2018年每個用戶下單數量,輸出:用戶id、下單數量,如下:

mysql> SELECT 
            user_id 用戶id, COUNT(id) 下單數量
        FROM
            t_order t
        WHERE
            t.the_year = 2018
        GROUP BY user_id;
+----------+--------------+
| 用戶id   | 下單數量     |
+----------+--------------+
|     1001 |            2 |
|     1002 |            3 |
|     1003 |            1 |
+----------+--------------+
3 rows in set (0.00 sec)

分組后篩選數據

分組后對數據篩選,使用having關鍵字

需求:查詢2018年訂單數量大於1的用戶,輸出:用戶id,下單數量,如下:

方式1:

mysql> SELECT
          user_id 用戶id, COUNT(id) 下單數量
        FROM
          t_order t
        WHERE
          t.the_year = 2018
        GROUP BY user_id
        HAVING count(id)>=2;
+----------+--------------+
| 用戶id   | 下單數量     |
+----------+--------------+
|     1001 |            2 |
|     1002 |            3 |
+----------+--------------+
2 rows in set (0.00 sec)

方式2:

mysql> SELECT
          user_id 用戶id, count(id) 下單數量
        FROM
          t_order t
        WHERE
          t.the_year = 2018
        GROUP BY user_id
        HAVING 下單數量>=2;
+----------+--------------+
| 用戶id   | 下單數量     |
+----------+--------------+
|     1001 |            2 |
|     1002 |            3 |
+----------+--------------+
2 rows in set (0.00 sec)

where和having的區別

where是在分組(聚合)前對記錄進行篩選,而having是在分組結束后的結果里篩選,最后返回整個sql的查詢結果。

可以把having理解為兩級查詢,即含having的查詢操作先獲得不含having子句時的sql查詢結果表,然后在這個結果表上使用having條件篩選出符合的記錄,最后返回這些記錄,因此,having后是可以跟聚合函數的,並且這個聚集函數不必與select后面的聚集函數相同。

分組后排序

需求:獲取每個用戶最大金額,然后按照最大金額倒序,輸出:用戶id,最大金額,如下:

mysql> SELECT
          user_id 用戶id, max(price) 最大金額
        FROM
          t_order t
        GROUP BY user_id
        ORDER BY 最大金額 desc;
+----------+--------------+
| 用戶id   | 最大金額     |
+----------+--------------+
|     1001 |        88.88 |
|     1003 |        66.66 |
|     1002 |        44.44 |
+----------+--------------+
3 rows in set (0.00 sec)

where & group by & having & order by & limit 一起協作

where、group by、having、order by、limit這些關鍵字一起使用時,先后順序有明確的限制,語法如下:

select 列 from 
表名
where [查詢條件]
group by [分組表達式]
having [分組過濾條件]
order by [排序條件]
limit [offset,] count;

注意:

寫法上面必須按照上面的順序來寫。

示例:

需求:查詢出2018年,下單數量大於等於2的,按照下單數量降序排序,最后只輸出第1條記錄,顯示:用戶id,下單數量,如下:

mysql> SELECT
          user_id 用戶id, COUNT(id) 下單數量
        FROM
          t_order t
        WHERE
          t.the_year = 2018
        GROUP BY user_id
        HAVING count(id)>=2
        ORDER BY 下單數量 DESC
        LIMIT 1;
+----------+--------------+
| 用戶id   | 下單數量     |
+----------+--------------+
|     1002 |            3 |
+----------+--------------+
1 row in set (0.00 sec)

mysql分組中的坑

本文開頭有介紹,分組中select后面的列只能有2種:

  1. 出現在group by后面的列
  2. 使用聚合函數的列

oracle、sqlserver、db2中也是按照這種規范來的。

文中使用的是5.7版本,默認是按照這種規范來的。

mysql早期的一些版本,沒有上面這些要求,select后面可以跟任何合法的列。

示例

需求:獲取每個用戶下單的最大金額及下單的年份,輸出:用戶id,最大金額,年份,寫法如下:

mysql> select
          user_id 用戶id, max(price) 最大金額, the_year 年份
        FROM t_order t
        GROUP BY t.user_id;
ERROR 1055 (42000): Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'javacode2018.t.the_year' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

上面的sql報錯了,原因因為the_year不符合上面說的2條規則(select后面的列必須出現在group by中或者使用聚合函數),而sql_mode限制了這種規則,我們看一下sql_mode的配置:

mysql> select @@sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode                                                                                                                                |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

sql_mode中包含了ONLY_FULL_GROUP_BY,這個表示select后面的列必須符合上面的說的2點規范。

可以將ONLY_FULL_GROUP_BY去掉,select后面就可以加任意列了,我們來看一下效果。

修改mysql中的my.ini文件:

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

重啟mysql,再次運行,效果如下:

mysql> select
          user_id 用戶id, max(price) 最大金額, the_year 年份
        FROM t_order t
        GROUP BY t.user_id;
+----------+--------------+--------+
| 用戶id   | 最大金額     | 年份   |
+----------+--------------+--------+
|     1001 |        88.88 |   2017 |
|     1002 |        44.44 |   2018 |
|     1003 |        66.66 |   2018 |
+----------+--------------+--------+
3 rows in set (0.03 sec)

看一下上面的數據,第一條88.88的年份是2017年,我們再來看一下原始數據:

mysql> select * from t_order;
+----+---------+---------------+-------+----------+
| id | user_id | user_name     | price | the_year |
+----+---------+---------------+-------+----------+
|  1 |    1001 | 路人甲Java    | 11.11 |     2017 |
|  2 |    1001 | 路人甲Java    | 22.22 |     2018 |
|  3 |    1001 | 路人甲Java    | 88.88 |     2018 |
|  4 |    1002 | 劉德華        | 33.33 |     2018 |
|  5 |    1002 | 劉德華        | 12.22 |     2018 |
|  6 |    1002 | 劉德華        | 16.66 |     2018 |
|  7 |    1002 | 劉德華        | 44.44 |     2019 |
|  8 |    1003 | 張學友        | 55.55 |     2018 |
|  9 |    1003 | 張學友        | 66.66 |     2019 |
+----+---------+---------------+-------+----------+
9 rows in set (0.00 sec)

對比一下,user_id=1001、price=88.88是第3條數據,即the_year是2018年,但是上面的分組結果是2017年,結果和我們預期的不一致,此時mysql對這種未按照規范來的列,亂序了,mysql取的是第一條。

正確的寫法,提供兩種,如下:

mysql> SELECT
          user_id 用戶id,
          price 最大金額,
          the_year 年份
        FROM
          t_order t1
        WHERE
          (t1.user_id , t1.price)
          IN
          (SELECT
             t.user_id, MAX(t.price)
           FROM
             t_order t
           GROUP BY t.user_id);
+----------+--------------+--------+
| 用戶id   | 最大金額     | 年份   |
+----------+--------------+--------+
|     1001 |        88.88 |   2018 |
|     1002 |        44.44 |   2019 |
|     1003 |        66.66 |   2019 |
+----------+--------------+--------+
3 rows in set (0.00 sec)

mysql> SELECT
          user_id 用戶id,
          price 最大金額,
          the_year 年份
        FROM
          t_order t1,(SELECT
                        t.user_id uid, MAX(t.price) pc
                      FROM
                        t_order t
                      GROUP BY t.user_id) t2
        WHERE
          t1.user_id = t2.uid
        AND  t1.price = t2.pc;
+----------+--------------+--------+
| 用戶id   | 最大金額     | 年份   |
+----------+--------------+--------+
|     1001 |        88.88 |   2018 |
|     1002 |        44.44 |   2019 |
|     1003 |        66.66 |   2019 |
+----------+--------------+--------+
3 rows in set (0.00 sec)

上面第1種寫法,比較少見,in中使用了多字段查詢。

建議:在寫分組查詢的時候,最好按照標准的規范來寫,select后面出現的列必須在group by中或者必須使用聚合函數。

總結

  1. 在寫分組查詢的時候,最好按照標准的規范來寫,select后面出現的列必須在group by中或者必須使用聚合函數
  2. select語法順序:select、from、where、group by、having、order by、limit,順序不能搞錯了,否則報錯。
  3. in多列查詢的使用,下去可以試試

Mysql系列目錄

  1. 第1篇:mysql基礎知識
  2. 第2篇:詳解mysql數據類型(重點)
  3. 第3篇:管理員必備技能(必須掌握)
  4. 第4篇:DDL常見操作
  5. 第5篇:DML操作匯總(insert,update,delete)
  6. 第6篇:select查詢基礎篇
  7. 第7篇:玩轉select條件查詢,避免采坑
  8. 第8篇:詳解排序和分頁(order by & limit)

mysql系列大概有20多篇,喜歡的請關注一下,歡迎大家加我微信itsoku或者留言交流mysql相關技術!

java高並發系列全集

  1. 第1天:必須知道的幾個概念
  2. 第2天:並發級別
  3. 第3天:有關並行的兩個重要定律
  4. 第4天:JMM相關的一些概念
  5. 第5天:深入理解進程和線程
  6. 第6天:線程的基本操作
  7. 第7天:volatile與Java內存模型
  8. 第8天:線程組
  9. 第9天:用戶線程和守護線程
  10. 第10天:線程安全和synchronized關鍵字
  11. 第11天:線程中斷的幾種方式
  12. 第12天JUC:ReentrantLock重入鎖
  13. 第13天:JUC中的Condition對象
  14. 第14天:JUC中的LockSupport工具類,必備技能
  15. 第15天:JUC中的Semaphore(信號量)
  16. 第16天:JUC中等待多線程完成的工具類CountDownLatch,必備技能
  17. 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
  18. 第18天:JAVA線程池,這一篇就夠了
  19. 第19天:JUC中的Executor框架詳解1
  20. 第20天:JUC中的Executor框架詳解2
  21. 第21天:java中的CAS,你需要知道的東西
  22. 第22天:JUC底層工具類Unsafe,高手必須要了解
  23. 第23天:JUC中原子類,一篇就夠了
  24. 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
  25. 第25天:掌握JUC中的阻塞隊列
  26. 第26篇:學會使用JUC中常見的集合,常看看!
  27. 第27天:實戰篇,接口性能提升幾倍原來這么簡單
  28. 第28天:實戰篇,微服務日志的傷痛,一並幫你解決掉
  29. 第29天:高並發中常見的限流方式
  30. 第30天:JUC中工具類CompletableFuture,必備技能
  31. 第31天:獲取線程執行結果,這6種方法你都知道?
  32. 第32天:高並發中計數器的實現方式有哪些?
  33. 第33篇:怎么演示公平鎖和非公平鎖?
  34. 第34篇:google提供的一些好用的並發工具類


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM