MySQL全面瓦解10:分組查詢和聚合函數

概述

相信咱們常常會遇到這樣的場景:想要了解雙十一天貓購買化妝品的人員中平均消費額度是多少(這可能有利於對商品價格區間的定位);或者不一樣年齡段的化妝品消費佔比是多少(這可能有助於對商品備貨量的預估)。html

這個時候就要用到分組查詢,分組查詢的目的是爲了把數據分紅多個邏輯組(購買化妝品的人員是一個組,不一樣年齡段購買化妝品的人員也是組),並對每一個組進行聚合計算的過程:。mysql

分組查詢的語法格式以下:sql

1 select cname, group_fun,... from tname [where condition]
2 group by group_expression [having group_condition]; 

說明一下:express

一、group_fun 表明聚合函數,是指對分組的數據進行聚合計算的函數。函數

二、group_expression 表明分組表達式,容許多個,多個之間使用逗號隔開。性能

三、group_condition 分組以後,再對分組後的數據進行條件過濾的過程。ui

四、分組語法中,select後面出現的字段 要麼是group by後面的字段,要麼是聚合函數的列,其餘類型會報異常,咱們下面的內容中會詳細說明。 spa

說分組以前,先來看看聚合函數,聚合函數是分組查詢語法格式中重要的一部分。咱們常常須要彙總數據而不用把它們實際檢索出來,因此MySQL供了專門的函數。使用這些函數,可用於計算咱們須要的數據,以便分析和生成報表。code

聚合函數

聚合函數有如下幾種。 htm

函數 說明
AVG() 返回指定字段的平均值
COUNT() 返回查詢結果行數
MAX() 返回指定字段的最大值 
MIN() 返回指定字段的最小值
SUM() 返回指定字段的求和值

AVG()函數

AVG()經過對錶中行數計數並計算特定列值之和,求得該列的平均值。 AVG()可用來返回全部列的平均值,也能夠用來返回特定列或行的平均值。

下面示例返回用戶表中用戶的平均年齡:

 1 mysql> select * from user2;
 2 +----+--------+------+----------+-----+
 3 | id | name   | age  | address  | sex |
 4 +----+--------+------+----------+-----+
 5 |  1 | brand  |   21 | fuzhou   |   1 |
 6 |  2 | helen  |   20 | quanzhou |   0 |
 7 |  3 | sol    |   21 | xiamen   |   0 |
 8 |  4 | weng   |   33 | guizhou  |   1 |
 9 |  5 | selina |   25 | NULL     |   0 |
10 |  6 | anny   |   23 | shanghai |   0 |
11 |  7 | annd   |   24 | shanghai |   1 |
12 |  8 | sunny  | NULL | guizhou  |   0 |
13 +----+--------+------+----------+-----+
14 8 rows in set
15 
16 mysql> select avg(age) from user2;
17 +----------+
18 | avg(age) |
19 +----------+
20 | 23.8571  |
21 +----------+
22 1 row in set 

注意點:

一、AVG()只能用來肯定特定數值列的平均值 。
二、AVG()函數忽略列值爲NULL的行,因此上圖中age值累加以後是除以7,而不是除以8。 

 

COUNT()函數

COUNT()函數進行計數。 能夠用COUNT()肯定表中符合條件的行的數目。

count 有 count(*)、count(具體字段)、count(常量) 三種方式來體現 下面 演示了count(*) 和 count(cname)的用法。

 1 mysql> select * from user2;
 2 +----+--------+------+----------+-----+
 3 | id | name   | age  | address  | sex |
 4 +----+--------+------+----------+-----+
 5 |  1 | brand  |   21 | fuzhou   |   1 |
 6 |  2 | helen  |   20 | quanzhou |   0 |
 7 |  3 | sol    |   21 | xiamen   |   0 |
 8 |  4 | weng   |   33 | guizhou  |   1 |
 9 |  5 | selina |   25 | NULL     |   0 |
10 |  6 | anny   |   23 | shanghai |   0 |
11 |  7 | annd   |   24 | shanghai |   1 |
12 |  8 | sunny  | NULL | guizhou  |   0 |
13 +----+--------+------+----------+-----+
14 8 rows in set
15 
16 mysql> select count(*) from user2 where sex=0;
17 +----------+
18 | count(*) |
19 +----------+
20 |        5 |
21 +----------+
22 1 row in set
23 
24 mysql> select count(age) from user2 where sex=0;
25 +------------+
26 | count(age) |
27 +------------+
28 |          4 |
29 +------------+
30 1 row in set 

能夠看到,都是取出女生的用戶數量,count(*) 比 count(age) 多一個,那是由於age中包含null值。

因此:若是指定列名,則指定列的值爲空的行被COUNT()函數忽略,但若是COUNT()函數中用的是星號( *),則不忽略。 

關於count 能夠看我寫的另外一篇,詳細分析了幾種count的使用和性能比較: SELECT COUNT 小結

MAX()和MIN()函數

MAX()返回指定列中的最大值,MIN()返回指定列中的最小值

 1 mysql> select * from user2;
 2 +----+--------+------+----------+-----+
 3 | id | name   | age  | address  | sex |
 4 +----+--------+------+----------+-----+
 5 |  1 | brand  |   21 | fuzhou   |   1 |
 6 |  2 | helen  |   20 | quanzhou |   0 |
 7 |  3 | sol    |   21 | xiamen   |   0 |
 8 |  4 | weng   |   33 | guizhou  |   1 |
 9 |  5 | selina |   25 | NULL     |   0 |
10 |  6 | anny   |   23 | shanghai |   0 |
11 |  7 | annd   |   24 | shanghai |   1 |
12 |  8 | sunny  | NULL | guizhou  |   0 |
13 +----+--------+------+----------+-----+
14 8 rows in set
15 
16 mysql> select max(age),min(age) from user2;
17 +----------+----------+
18 | max(age) | min(age) |
19 +----------+----------+
20 |       33 |       20 |
21 +----------+----------+
22 1 row in set 

 注意:一樣的,MAX()、MIN()函數忽略列值爲NULL的行。

SUM函數

SUM()用來返回指定列值的和(總計) ,下面返回了全部年齡的總和,一樣的,忽略了null的值

 1 mysql> select * from user2;
 2 +----+--------+------+----------+-----+
 3 | id | name   | age  | address  | sex |
 4 +----+--------+------+----------+-----+
 5 |  1 | brand  |   21 | fuzhou   |   1 |
 6 |  2 | helen  |   20 | quanzhou |   0 |
 7 |  3 | sol    |   21 | xiamen   |   0 |
 8 |  4 | weng   |   33 | guizhou  |   1 |
 9 |  5 | selina |   25 | NULL     |   0 |
10 |  6 | anny   |   23 | shanghai |   0 |
11 |  7 | annd   |   24 | shanghai |   1 |
12 |  8 | sunny  | NULL | guizhou  |   0 |
13 +----+--------+------+----------+-----+
14 8 rows in set
15 
16 mysql> select sum(age) from user2;
17 +----------+
18 | sum(age) |
19 +----------+
20 | 167      |
21 +----------+
22 1 row in set

分組查詢

數據準備,假設咱們有一個定貨單表以下(記載用戶的訂單金額和下單時間):

 1 mysql> select * from t_order;
 2 +---------+-----+-------+--------+---------------------+------+
 3 | orderid | uid | uname | amount | time                | year |
 4 +---------+-----+-------+--------+---------------------+------+
 5 |      20 |   1 | brand | 91.23  | 2018-08-20 17:22:21 | 2018 |
 6 |      21 |   1 | brand | 87.54  | 2019-07-16 09:21:30 | 2019 |
 7 |      22 |   1 | brand | 166.88 | 2019-04-04 12:23:55 | 2019 |
 8 |      23 |   2 | helyn | 93.73  | 2019-09-15 10:11:11 | 2019 |
 9 |      24 |   2 | helyn | 102.32 | 2019-01-08 17:33:25 | 2019 |
10 |      25 |   2 | helyn | 106.06 | 2019-12-24 12:25:25 | 2019 |
11 |      26 |   2 | helyn | 73.42  | 2020-04-03 17:16:23 | 2020 |
12 |      27 |   3 | sol   | 55.55  | 2019-08-05 19:16:23 | 2019 |
13 |      28 |   3 | sol   | 69.96  | 2020-09-16 19:23:16 | 2020 |
14 |      29 |   4 | weng  | 199.99 | 2020-06-08 19:55:06 | 2020 |
15 +---------+-----+-------+--------+---------------------+------+
16 10 rows in set 

單字段分組

即對於某個字段進行分組,好比針對用戶進行分組,輸出他們的用戶Id,訂單數量和總額:

 1 mysql> select uid,count(uid),sum(amount) from t_order group by uid;
 2 +-----+------------+-------------+
 3 | uid | count(uid) | sum(amount) |
 4 +-----+------------+-------------+
 5 |   1 |          3 | 345.65      |
 6 |   2 |          4 | 375.53      |
 7 |   3 |          2 | 125.51      |
 8 |   4 |          1 | 199.99      |
 9 +-----+------------+-------------+
10 4 rows in set 

多字段分組

即對於多個字段進行分組,好比針對用戶進行分組,再對他們不一樣年份的訂單數據進行分組,輸出訂單數量和消費總額:

 1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount,year from t_order group by uid,year;
 2 +-----+------+-------------+------+
 3 | uid | nums | totalamount | year |
 4 +-----+------+-------------+------+
 5 |   1 |    1 | 91.23       | 2018 |
 6 |   1 |    2 | 254.42      | 2019 |
 7 |   2 |    3 | 302.11      | 2019 |
 8 |   2 |    1 | 73.42       | 2020 |
 9 |   3 |    1 | 55.55       | 2019 |
10 |   3 |    1 | 69.96       | 2020 |
11 |   4 |    1 | 199.99      | 2020 |
12 +-----+------+-------------+------+
13 7 rows in set 

分組前的條件過濾:where

這個很簡單,就是再分組(group by)以前經過where關鍵字進行條件過濾,取出咱們須要的數據,假設咱們只要列出2019年8月以後的數據,源數據只有6條合格的,有兩條年份同樣被分組的:

 1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount,year from t_order where time > '2019-08-01' group by uid,year;
 2 +-----+------+-------------+------+
 3 | uid | nums | totalamount | year |
 4 +-----+------+-------------+------+
 5 |   2 |    2 | 199.79      | 2019 |
 6 |   2 |    1 | 73.42       | 2020 |
 7 |   3 |    1 | 55.55       | 2019 |
 8 |   3 |    1 | 69.96       | 2020 |
 9 |   4 |    1 | 199.99      | 2020 |
10 +-----+------+-------------+------+
11 5 rows in set 

分組後的條件過濾:having

有時候咱們須要再分組以後再對數據進行過濾,這時候就須要使用having關鍵字進行數據過濾,再上述條件下,咱們須要取出消費次數超過一次的數據:

1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount,year from t_order where time > '2019-08-01' group by uid,year having nums>1;
2 +-----+------+-------------+------+
3 | uid | nums | totalamount | year |
4 +-----+------+-------------+------+
5 |   2 |    2 | 199.79      | 2019 |
6 +-----+------+-------------+------+
7 1 row in set 

這邊須要注意區分where和having:

where是在分組(聚合)前對記錄進行篩選,而having是在分組結束後的結果裏篩選,最後返回過濾後的結果。

能夠把having理解爲兩級查詢,即含having的查詢操做先得到不含having子句時的sql查詢結果表,而後在這個結果表上使用having條件篩選出符合的記錄,最後返回這些記錄,所以,having後是能夠跟聚合函數的,而且這個彙集函數沒必要與select後面的彙集函數相同。

分組後的排序處理

order條件接在group by後面,也就是統計出每一個用戶的消費總額和消費次數後,對用戶的消費總額進行降序排序的過程。

 1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount from t_order group by uid;
 2 +-----+------+-------------+
 3 | uid | nums | totalamount |
 4 +-----+------+-------------+
 5 |   1 |    3 | 345.65      |
 6 |   2 |    4 | 375.53      |
 7 |   3 |    2 | 125.51      |
 8 |   4 |    1 | 199.99      |
 9 +-----+------+-------------+
10 4 rows in set
11 
12 mysql> select uid,count(uid) as nums,sum(amount) as totalamount from t_order group by uid order by totalamount desc; 13 +-----+------+-------------+
14 | uid | nums | totalamount |
15 +-----+------+-------------+
16 |   2 |    4 | 375.53      |
17 |   1 |    3 | 345.65      |
18 |   4 |    1 | 199.99      |
19 |   3 |    2 | 125.51      |
20 +-----+------+-------------+
21 4 rows in set 

分組後的limit 限制

limit限制關鍵字通常放在語句的最末尾,好比基於咱們上面的搜索,咱們再limit 1,只取出消費額最高的那條,其餘跳過。

1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount from t_order group by uid order by totalamount desc limit 1;
2 +-----+------+-------------+
3 | uid | nums | totalamount |
4 +-----+------+-------------+
5 |   2 |    4 | 375.53      |
6 +-----+------+-------------+
7 1 row in set 

關鍵字的執行順序

咱們看到上面那咱們用了 where、group by、having、order by、limit這些關鍵字,若是一塊兒使用,他們是有前後順序,順序錯了會致使異常,語法格式以下:

1 select cname from tname
2 where [原表查詢條件]
3 group by [分組表達式]
4 having [分組過濾條件]
5 order by [排序條件]
6 limit [offset,] count;

 

1 mysql> select uid,count(uid) as nums,sum(amount) as totalamount from t_order where time > '2019-08-01' group by uid having totalamount>100 order by totalamount desc limit 1;
2 +-----+------+-------------+
3 | uid | nums | totalamount |
4 +-----+------+-------------+
5 |   2 |    3 | 273.21      |
6 +-----+------+-------------+
7 1 row in set

 

總結

一、分組語法中,select後面出現的字段 要麼是group by後面的字段,要麼是聚合函數的列,其餘類型會報異常:能夠本身試試。

二、分組關鍵字的執行順序:where、group by、having、order by、limit,順序不能調換,不然會報異常:能夠本身試試。

相關文章
相關標籤/搜索