班級表 banji(id, class_name),學生表 user(id, name, class_id)。需求:查詢所有班級及其學生名單,**每班一行**,學生名以分隔符拼接。
SELECT
b.*,
GROUP_CONCAT(u.name SEPARATOR '|') AS student_names
FROM banji b
LEFT JOIN user u ON b.id = u.class_id
WHERE b.id = 2
GROUP BY b.id;
| 方案 | 返回行數 | 學生欄位 |
|---|---|---|
| 普通 JOIN | 學生數 N 行 | 單個名字,重複班級信息 |
| GROUP_CONCAT | 1 行 | lisi|zhaoliu|liu|小二|xiaoming|小張 |
GROUP_CONCAT(DISTINCT u.name ORDER BY u.name SEPARATOR ',')
DISTINCT:去重(防止髒數據重複)。ORDER BY:組內排序,保證輸出確定性。SEPARATOR:默認逗號,可改 |、;、\n 等。SELECT
b.id,
b.class_name,
GROUP_CONCAT(CONCAT(u.id, ':', u.name) ORDER BY u.id SEPARATOR ';') AS students
FROM banji b
LEFT JOIN user u ON b.id = u.class_id
GROUP BY b.id, b.class_name;
輸出:1:lisi;2:zhaoliu;3:liu,應用層 split(';') → split(':') 解析。
SELECT
b.id,
b.class_name,
JSON_ARRAYAGG(JSON_OBJECT('id', u.id, 'name', u.name) ORDER BY u.id) AS students
FROM banji b
LEFT JOIN user u ON b.id = u.class_id
GROUP BY b.id, b.class_name;
輸出:[{"id":1,"name":"lisi"},{"id":2,"name":"zhaoliu"}],前端直食,無需解析。
| 限制 | 默認值 | 調整方法 | 影響 |
|---|---|---|---|
group_concat_max_len | 1024 字節 | SET SESSION group_concat_max_len = 102400; | 超長截斷,導致數據不全 |
max_allowed_packet | 4MB/64MB | SET GLOBAL max_allowed_packet = 1G; | 結果包過大報錯 |
最佳實踐:會話級設置足夠大值,避免全局副作用。
GROUP_CONCAT 需將分組內所有行拼接,**內存中構建字符串**,大分組 (如 10 萬用戶) 會佔用大量內存、CPU。user(class_id, name) 覆蓋索引,避免回表。| 數據庫 | 聚合函數 | 備註 |
|---|---|---|
| MySQL | GROUP_CONCAT / JSON_ARRAYAGG | 最靈活 |
| PostgreSQL | STRING_AGG / JSONB_AGG | 功能對標 |
| Oracle | LISTAGG / WM_CONCAT (需安裝包) | WM_CONCAT 非標準 |
| SQL Server | STRING_AGG (2017+) / FOR XML PATH | 舊版繞道 XML |
-- 文章標籤聚合
SELECT
a.id,
a.title,
GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ',') AS tags
FROM article a
LEFT JOIN article_tag at ON a.id = at.article_id
LEFT JOIN tag t ON at.tag_id = t.id
GROUP BY a.id, a.title;
掌握 GROUP_CONCAT 及其現代替代者 JSON_ARRAYAGG,讓一對多查詢結果直接服務於 API 響應,減少應用層 N+1 查詢與拼接邏輯。