Mysql高手系列 - 第9篇:詳解分組查詢,mysql分組有大坑!
這是Mysql系列第9篇。
環境:mysql5.7.25,cmd命令中進行演示。
本篇內容
- 分組查詢語法
- 聚合函式
- 單欄位分組
- 多欄位分組
- 分組前篩選資料
- 分組後篩選資料
- where和having的區別
- 分組後排序
- where & group by & having & order by & limit 一起協作
- mysql分組中的坑
- 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後面只能有兩種型別的列:
- 出現在group by後的列
- 或者使用聚合函式的列
聚合函式
函式名稱 | 作用 |
---|---|
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種:
- 出現在group by後面的列
- 使用聚合函式的列
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中或者必須使用聚合函式。
總結
- 在寫分組查詢的時候,最好按照標準的規範來寫,select後面出現的列必須在group by中或者必須使用聚合函式。
- select語法順序:select、from、where、group by、having、order by、limit,順序不能搞錯了,否則報錯。
- in多列查詢的使用,下去可以試試
Mysql系列目錄
- 第1篇:mysql基礎知識
- 第2篇:詳解mysql資料型別(重點)
- 第3篇:管理員必備技能(必須掌握)
- 第4篇:DDL常見操作
- 第5篇:DML操作彙總(insert,update,delete)
- 第6篇:select查詢基礎篇
- 第7篇:玩轉select條件查詢,避免採坑
- 第8篇:詳解排序和分頁(order by & limit)
mysql系列大概有20多篇,喜歡的請關注一下,歡迎大家加我微信itsoku或者留言交流mysql相關技術!
java高併發系列全集
- 第1天:必須知道的幾個概念
- 第2天:併發級別
- 第3天:有關並行的兩個重要定律
- 第4天:JMM相關的一些概念
- 第5天:深入理解程序和執行緒
- 第6天:執行緒的基本操作
- 第7天:volatile與Java記憶體模型
- 第8天:執行緒組
- 第9天:使用者執行緒和守護執行緒
- 第10天:執行緒安全和synchronized關鍵字
- 第11天:執行緒中斷的幾種方式
- 第12天JUC:ReentrantLock重入鎖
- 第13天:JUC中的Condition物件
- 第14天:JUC中的LockSupport工具類,必備技能
- 第15天:JUC中的Semaphore(訊號量)
- 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能
- 第17天:JUC中的迴圈柵欄CyclicBarrier的6種使用場景
- 第18天:JAVA執行緒池,這一篇就夠了
- 第19天:JUC中的Executor框架詳解1
- 第20天:JUC中的Executor框架詳解2
- 第21天:java中的CAS,你需要知道的東西
- 第22天:JUC底層工具類Unsafe,高手必須要了解
- 第23天:JUC中原子類,一篇就夠了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞佇列
- 第26篇:學會使用JUC中常見的集合,常看看!
- 第27天:實戰篇,介面效能提升幾倍原來這麼簡單
- 第28天:實戰篇,微服務日誌的傷痛,一併幫你解決掉
- 第29天:高併發中常見的限流方式
- 第30天:JUC中工具類CompletableFuture,必備技能
- 第31天:獲取執行緒執行結果,這6種方法你都知道?
- 第32天:高併發中計數器的實現方式有哪些?
- 第33篇:怎麼演示公平鎖和非公平鎖?
- 第34篇:google提供的一些好用的併發工具類