一、背景
在上篇文章《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
加油,技術人。