MYSQL 子查詢聚合,90% 的人都不會

一、背景
在上篇文章《mysql 面試題:多值字符串如何聯表查詢?》分享瞭如何對字符串做子查詢。

最終結果像下面的樣子。

select A.c_name, A.c_users, B.c_id, B.c_name 
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users);

+--------+----------+------+--------+
| c_name | c_users  | c_id | c_name |
+--------+----------+------+--------+
| one    | b,c,d,aa | aa   | aa     |
| one    | b,c,d,aa | b    | bb     |
| one    | b,c,d,aa | c    | cc     |
| one    | b,c,d,aa | d    | dd     |
+--------+----------+------+--------+

如果你是一個有經驗的程序員,可以一眼發現,前面的幾列非常冗餘。

於是便有了新的問題:MYSQL 子查詢結果如何聚合在一起呢?

二、聚合爲計數
如果僅僅需要聚合後的數量,使用 count 函數就可以了。

這個在文章《mysql 的 count(*) 與 count(1)》已經分享,這裏就不做過多的介紹了。

這裏只羅列下語句與結果。

mysql> select A.c_name, A.c_users, count(*)  user_count
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users) 
group by c_name;

+--------+----------+------------+
| c_name | c_users  | user_count |
+--------+----------+------------+
| one    | b,c,d,aa |          4 |
+--------+----------+------------+

當然,也有人說可以直接從 c_users 得到 count。
比如需要字符串拆分、去重、統計。
那個是另外的技術了,感興趣的話你可以想想怎麼直接從 A 表得到 用戶的個數。

三、聚合爲一行一列
MYSQL 中有一個 CONCAT 函數,可以將一行中的多列聚合爲一列。
同樣的,還有一個 GROUP_CONCAT 函數,可以將多行的內容聚合爲一行的一列。

通過這個函數,我們就可以將子查詢的內容聚合爲 A 表的一列了。

語法:

GROUP_CONCAT([DISTINCT] expr [,expr ...]
             [ORDER BY {unsigned_integer | col_name | expr}
                 [ASC | DESC] [,col_name ...]]
             [SEPARATOR str_val])

語法看起來很複雜,這裏少做解釋。

-)DISTINCT 對後面的表達式列表去重
-)ORDER BY 由於輸出的是拼接的字符串,有先後順序,排序規則 -)[ASC | DESC] 升序降序開關
-)[,col_name …] 指定多維度排序
-)SEPARATOR 多行之間的分隔符

最簡單的例子如下:

mysql> select A.c_name, A.c_users, GROUP_CONCAT( B.c_name)  user_info
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users) 
group by c_name;

+--------+----------+-------------+
| c_name | c_users  | user_info   |
+--------+----------+-------------+
| one    | b,c,d,aa | aa,bb,cc,dd |
+--------+----------+-------------+

此時可能就有人問:這裏只對多行一列聚合了,怎麼對多行多列聚合。

大家還記得這一小節的第一句話嗎?
MYSQL 中有一個 CONCAT 函數,可以將一行中的多列聚合爲一列。

CONCAT 怎麼做到將多列聚合,GROUP_CONCAT 就可以用想用的方法做到。

還不明白,看下面的例子就明白了,表達式列表就是要拼接的多列字段。

select A.c_name, A.c_users, GROUP_CONCAT(B.c_id, '|', B.c_name)  user_info
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users) 
group by c_name;

+--------+----------+----------------------+
| c_name | c_users  | user_info            |
+--------+----------+----------------------+
| one    | b,c,d,aa | aa|aa,b|bb,c|cc,d|dd |
+--------+----------+----------------------+

四、聚合爲數組
上面介紹瞭如何將多行多列聚合爲一行一列。

但是有小朋友不滿意了:
聚合的結果是特殊字符拼接的字符串,對於業務來說一點都不好用。
能不能聚合爲結構化的結果呢,比如數組 json。

這麼一說,發現還真有函數可以做到。

沒錯,就是 JSON_ARRAYAGG 。

mysql> select A.c_name, A.c_users, JSON_ARRAYAGG( B.c_name)  user_info
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users) 
group by A.c_name;

+--------+----------+-----------------------+
| c_name | c_users  | user_info             |
+--------+----------+-----------------------+
| one    | b,c,d,aa | ["aa","bb","cc","dd"] |
+--------+----------+-----------------------+

這個轉數組 json 很好用,但是很可惜不支持指定排序。

五、聚合爲對象
上面聚合爲數組只支持聚合多行一列爲一行一列。
如果有多行多列的話,該如何操作呢?

查看 MYSQL 的官方文檔,發現剩餘的函數只剩下一個函數了。
那就是 JSON_OBJECTAGG 。

但是閱讀下文檔,會發現這個函數只能聚合兩列,一列作爲 Key,一列作爲 Value。
如果有重複的 key,將會只保留最後一個。

mysql> select A.c_name, A.c_users, JSON_OBJECTAGG(B.c_id, B.c_name)  user_info
from A, B 
where A.c_id = 'tk' and FIND_IN_SET(B.c_id, A.c_users) 
group by A.c_name;

+--------+----------+------------------------------------------+
| c_name | c_users  | user_info                                |
+--------+----------+------------------------------------------+
| one    | b,c,d,aa | {"aa":"aa","b":"bb","c":"cc","d":"dd"}   |
+--------+----------+------------------------------------------+

六、最後
可以發現, MYSQL 內置了四個聚合函數。
分別是計數、拼接字符串、JSON 數組、JSON 鍵值對。

至於更復雜的聚合需求,就需要我們自己想辦法實現了。

問題:比如多行三列,如何聚合爲一個 JSON 字段呢?

參考文檔:

https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html

加油,技術人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章