首先我們來建立一個測試的表和數據,代碼如下
CREATE TABLE `per` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pname` varchar(50) DEFAULT NULL,
`page` int(11) DEFAULT NULL,
`psex` varchar(50) DEFAULT NULL,
`paddr` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;
INSERT INTO `per` VALUES ('1', '王小華', '30', '男', '北京');
INSERT INTO `per` VALUES ('2', '張文軍', '24', '男', '上海');
INSERT INTO `per` VALUES ('3', '羅敏', '19', '女', '重慶');
INSERT INTO `per` VALUES ('4', '張建新', '32', '男', '重慶');
INSERT INTO `per` VALUES ('5', '劉婷', '26', '女', '成都');
INSERT INTO `per` VALUES ('6', '劉小亞', '22', '女', '重慶');
INSERT INTO `per` VALUES ('7', '王建軍', '22', '男', '貴州');
INSERT INTO `per` VALUES ('8', '謝濤', '28', '男', '海南');
INSERT INTO `per` VALUES ('9', '張良', '26', '男', '上海');
INSERT INTO `per` VALUES ('10', '黎記', '17', '男', '貴陽');
INSERT INTO `per` VALUES ('11', '趙小麗', '26', '女', '上海');
INSERT INTO `per` VALUES ('12', '張三', null, '女', '北京');
concat()函數
首先我們先學一個函數叫concat()函數, 這個函數非常簡單
功能:就是將多個字符串連接成一個字符串
語法:concat(字符串1, 字符串2,...) 字符串參數用逗號隔開!
返回值: 結果為連接參數產生的字符串,如果有任何一個參數為null,則返回值為null。
案例1
select concat('重慶','北京','上海');
效果如下圖: 是不是覺得很簡單 很直觀呢!

案例2
這有一張表
+----+-----------+------+------+--------+
| id | pname | page | psex | paddr |
+----+-----------+------+------+--------+
| 1 | 王小華 | 30 | 男 | 北京 |
| 2 | 張文軍 | 24 | 男 | 上海 |
| 3 | 羅敏 | 19 | 女 | 重慶 |
| 4 | 張建新 | 32 | 男 | 重慶 |
| 5 | 劉婷 | 26 | 女 | 成都 |
| 6 | 劉小亞 | 22 | 女 | 重慶 |
| 7 | 王建軍 | 22 | 男 | 貴州 |
| 8 | 謝濤 | 28 | 男 | 海南 |
| 9 | 張良 | 26 | 男 | 上海 |
| 10 | 黎記 | 17 | 男 | 貴陽 |
| 11 | 趙小麗 | 26 | 女 | 上海 |
| 12 | 張三 | NULL | 女 | 北京 |
+----+-----------+------+------+--------+
#-- 執(zhí)行如下語句
select concat(pname,page,psex) from per;
#--結果
+-------------------------+
| concat(pname,page,psex) |
+-------------------------+
| 王小華30男 |
| 張文軍24男 |
| 羅敏19女 |
| 張建新32男 |
| 劉婷26女 |
| 劉小亞22女 |
| 王建軍22男 |
| 謝濤28男 |
| 張良26男 |
| 黎記17男 |
| 趙小麗26女 |
| NULL |
+-------------------------+
#--為什么會有一條是NULL呢?
#--那是因為第12條數據中的page字段為空,根據有一個字段為空結果就為NULL的理論推導出 查詢出的最后一條記錄為NULL!
但是大家一定會發(fā)現雖然連在一起顯示了 但是彼此沒有分隔符啊 看起來好難受 對不對? 所以接下來我們就來講講衍生出來的 concat_ws()函數
concat_ws()函數
功能:concat_ws()函數 和 concat()函數一樣,也是將多個字符串連接成一個字符串,但是可以指定分隔符!
語法:concat_ws(separator, str1, str2, ...) 第一個參數指定分隔符, 后面依舊是字符串
separator就是分隔符字符!
需要注意的是分隔符不能為null,如果為null,則返回結果為null。
案例代碼:
select concat_ws(',',pname,page,psex) from per;
#--以逗號分割 結果如下
+--------------------------------+
| concat_ws(',',pname,page,psex) |
+--------------------------------+
| 王小華,30,男 |
| 張文軍,24,男 |
| 羅敏,19,女 |
| 張建新,32,男 |
| 劉婷,26,女 |
| 劉小亞,22,女 |
| 王建軍,22,男 |
| 謝濤,28,男 |
| 張良,26,男 |
| 黎記,17,男 |
| 趙小麗,26,女 |
| 張三,女 |
+--------------------------------+
#--把分隔符指定為null,結果全部變成了null
select concat_ws(null,pname,page,psex) from per; #--錯誤的
+---------------------------------+
| concat_ws(null,pname,page,psex) |
+---------------------------------+
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
| NULL |
+---------------------------------+
group_concat()函數
接下來就要進入我們本文的主題了,group_concat()函數, 理解了上面兩個函數的作用和用法 就對理解group_concat()函數有很大幫助了!
功能:將group by產生的同一個分組中的值連接起來,返回一個字符串結果。
語法:group_concat( [distinct] 要連接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )
注意: 中括號是可選的
分析: 通過使用distinct可以排除重復值;如果希望對結果中的值進行排序,可以使用order by子句;separator是一個字符串值,缺省為一個逗號。
以下我準備了幾個案例 小伙伴們可以選擇性的去閱讀 并且把代碼復制到MySQL中執(zhí)行以下就可以知道用法了!
重點注意
1.group_concat只有與group by語句同時使用才能產生效果 所以使用 GROUP_CONCAT()函數必須對源數據進行分組,否則所有數據會被合并成一行
2.需要將拼接的結果去重的話,可與DISTINCT結合使用即可
案例1
需求: 比如我們要查在重慶的有哪些人? 并且把這些人的名字用 '-' 字符分隔開 然后顯示出來, SQL語句如下
#--這里就用到了 : 取出重復、顯示排序、 定義分隔字符
select
paddr,
group_concat(distinct pname order by pname desc separator '-') as '人'
from per
group by paddr;
#--結果為:
+--------+----------------------------+
| paddr | 人 |
+--------+----------------------------+
| 上海 | 趙小麗-張良-張文軍 |
| 北京 | 王小華-張三 |
| 成都 | 劉婷 |
| 海南 | 謝濤 |
| 貴州 | 王建軍 |
| 貴陽 | 黎記 |
| 重慶 | 羅敏-張建新-劉小亞 |
+--------+----------------------------+
#--有多個的自然會被用字符分隔連接起來,只有一個人的就沒有什么變化!直接顯示
案例2
需求: 比如我們要查在重慶的有哪些人? 并且把這些人的名字用逗號隔開,
以上需求跟上面的案例1 差不多 我們就加一個效果, 也就是顯示出來的名字前面把id號 也加上
#--顯示出來每一個名字所對應的id號 這里我們結合了group_concat()函數 和 concat_ws()函數,
select
paddr,
group_concat(concat_ws('-',id,pname) order by id asc) as '人'
from per
group by paddr;
#--顯示結果
+--------+-----------------------------------+
| paddr | 人 |
+--------+-----------------------------------+
| 上海 | 2-張文軍,9-張良,11-趙小麗 |
| 北京 | 1-王小華,12-張三 |
| 成都 | 5-劉婷 |
| 海南 | 8-謝濤 |
| 貴州 | 7-王建軍 |
| 貴陽 | 10-黎記 |
| 重慶 | 3-羅敏,4-張建新,6-劉小亞 |
+--------+-----------------------------------+
注意:
1.MySQL中函數是可以嵌套使用的
2.一般使用group_concat()函數,必須是存在group by 分組的情況下 才能使用這個函數
案例3
我們再來看一個案例, 首先我們準備以下測試數據
準備一個student學生表、MySQL代碼如下
#-- student
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT, #--id
`stuName` varchar(22) DEFAULT NULL, #--學生姓名
`course` varchar(22) DEFAULT NULL, #--學習科目
`score` int(11) DEFAULT NULL, #--學分
PRIMARY KEY (`id`) #--設置主鍵
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; #--設置表引擎 自動遞增起始值 默認編碼格式
-- ----------------------------
-- 插入以下數據
-- ----------------------------
INSERT INTO `student`(stuName,course,score) VALUES ('張三', '語文', '91');
INSERT INTO `student`(stuName,course,score) VALUES ('張三', '數學', '90');
INSERT INTO `student`(stuName,course,score) VALUES ('張三', '英語', '87');
INSERT INTO `student`(stuName,course,score) VALUES ('李四', '語文', '79');
INSERT INTO `student`(stuName,course,score) VALUES ('李四', '數學', '95');
INSERT INTO `student`(stuName,course,score) VALUES ('李四', '英語', '80');
INSERT INTO `student`(stuName,course,score) VALUES ('王五', '語文', '77');
INSERT INTO `student`(stuName,course,score) VALUES ('王五', '數學', '81');
INSERT INTO `student`(stuName,course,score) VALUES ('王五', '英語', '89');
#--建立好之后 數據如下顯示:
mysql> select * from student;
+----+---------+--------+-------+
| id | stuName | course | score |
+----+---------+--------+-------+
| 10 | 張三 | 語文 | 91 |
| 11 | 張三 | 數學 | 90 |
| 12 | 張三 | 英語 | 87 |
| 13 | 李四 | 語文 | 79 |
| 14 | 李四 | 數學 | 95 |
| 15 | 李四 | 英語 | 80 |
| 16 | 王五 | 語文 | 77 |
| 17 | 王五 | 數學 | 81 |
| 18 | 王五 | 英語 | 89 |
+----+---------+--------+-------+
建立好表和數據之后 我們就來繼續(xù)使用group_concat()函數 加深以下印象!
需求1: 以stuName學生名稱分組,把得分數score字段的值打印在一行,逗號分隔(默認) SQL如下
select stuName, GROUP_CONCAT(score) as '當前這個學生的得分數' from student GROUP BY stuName;
#--運行結果如下
mysql> select stuName, GROUP_CONCAT(score) as '當前這個學生的得分數' from student GROUP BY stuName;
+---------+--------------------------------+
| stuName | 當前這個學生的得分數 |
+---------+--------------------------------+
| 張三 | 91,90,87 |
| 李四 | 79,95,80 |
| 王五 | 77,81,89 |
+---------+--------------------------------+
需求2: 那么根據上面的結果 我們看到分數是出來了 但是不知道是什么科目分數 那么我們還要把科目也連起來顯示,并且分數還是從小到大,我們應該怎么做呢 ? 其實很簡單的啦 SQL如下
select stuName, GROUP_CONCAT(concat_ws('=',course,score) order by score asc) as '當前這個學生的得分數' from student GROUP BY stuName;
#--執(zhí)行結果如下
+---------+--------------------------------+
| stuName | 當前這個學生的得分數 |
+---------+--------------------------------+
| 張三 | 英語=87,數學=90,語文=91 |
| 李四 | 語文=79,英語=80,數學=95 |
| 王五 | 語文=77,數學=81,英語=89 |
+---------+--------------------------------+
#-- 這樣顯示是不是覺得更加清楚了呢!
需求3: 這里再給小伙伴深入一個問題 那么我們現在要查詢出 語文、數學、外語 三門課的最低分,還有哪個學生考的? 現在應該怎么寫呢?
方法1
#--首先我們可以把這個問題拆分成兩個部分
#--1.就是找出語文、數學、外語 三門課的最低分 這一步還是比較簡單的我們可以使用分組查詢就可以解決
#--分析問題后得出SQL方案 按照科目進行分組查詢 然后使用聚合函數篩選出最小的得分數, 顯示對應科目字段 這樣就得出了三門課的最低分
SELECT min(score),course FROM student GROUP BY course;
#--那么查詢出的結果如下
+------------+--------+
| min(score) | course |
+------------+--------+
| 81 | 數學 |
| 80 | 英語 |
| 77 | 語文 |
+------------+--------+
#--那么接下來我們要考慮的是如何找到是哪個學生考的!?
#--這里我們可以使用in() 的包含+ 子查詢的方式來 根據上面SQL的結果 來進行匹配包含查詢 學生名
SELECT stuName,score,course from student where (score,course) in(SELECT min(score),course FROM student GROUP BY course);
#--結果如下
+---------+-------+--------+
| stuName | score | course |
+---------+-------+--------+
| 李四 | 80 | 英語 |
| 王五 | 77 | 語文 |
| 王五 | 81 | 數學 |
+---------+-------+--------+
問題分析
1.這里的重點就在于子查詢的使用 上面已經用一句SQL查詢出了 三門課的最低分和科目 那么我們就可以列用這個結果集來 當做另外一句SQL所要查詢條件 !
2.where 后面跟的是一個圓括號 里面寫的是 分數和科目兩個字段,用來匹配in() 里面的子查詢結果 可能這里有些新手小伙伴并沒有見過這樣寫 現在應該清楚了
方法2
#--我們也可以用以下SQL語句來實現 ,性能上比上面好一點點!
SELECT g.`id`,g.`course`,g.`score`,g.`stuName`FROM (SELECT course, SUBSTRING_INDEX(GROUP_CONCAT(score ORDER BY score ASC), ',',1) AS score FROM student GROUP BY course) AS t LEFT JOIN student AS g ON (t.course = g.`course` AND t.score = g.`score`)
#--小提示:SUBSTRING_INDEX() 函數是提取的連接字符中的第一個
#--簡單的說 先連接好分數字段中的得分默認用逗號 再從分數連接字符中提取第一個出來,
#--首先我們要得到每一個科目中最小的分數 我們可以分析出如下SQL,
#--這里的分組條件還是以科目進行分組, 分組之后還是GROUP_CONCAT()函數用逗號連接起相對應的所有分數,然后用SUBSTRING_INDEX()函數提取連接字符中的第一個字符作為結果
SELECT course,SUBSTRING_INDEX(GROUP_CONCAT(score ORDER BY score ASC),',',1) AS score FROM student GROUP BY course;
#--結果如下
+--------+-------+
| course | score |
+--------+-------+
| 數學 | 81 |
| 英語 | 80 |
| 語文 | 77 |
+--------+-------+
#--我們可以把這個結果 想象成一張?zhí)摂M表取一個別名 t, 現在t這個是一個臨時的表,我們要查詢id,科目,分數,姓名, 就在前面加上需要的字段,注意別名
#--然后再使用左連接篩選出 對應的結果
SELECT g.`id`,g.`course`,g.`score`,g.`stuName`FROM
(SELECT course,SUBSTRING_INDEX(GROUP_CONCAT(score ORDER BY score ASC),',',1) AS score FROM student GROUP BY course) as t
LEFT JOIN student AS g ON (t.course = g.`course` AND t.score = g.`score`) #--left join 來顯示出符合條件的結果 也就是用上面查詢出來的結果來對應條件
#--結果如下
+------+--------+-------+---------+
| id | course | score | stuName |
+------+--------+-------+---------+
| 15 | 英語 | 80 | 李四 |
| 16 | 語文 | 77 | 王五 |
| 17 | 數學 | 81 | 王五 |
+------+--------+-------+---------+
案例4
我們來簡單的準備一個商品表吧 , 代碼如下
#-- goods
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT, #--id
`price` varchar(22) DEFAULT NULL, #--商品價格
`goods_name` varchar(22) DEFAULT NULL, #--商品名稱
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; #--設置表引擎 自動遞增起始值 默認編碼格式
-- ----------------------------
-- 插入以下數據
-- ----------------------------
INSERT INTO `goods`(price,goods_name) VALUES (10.00, '皮包');
INSERT INTO `goods`(price,goods_name) VALUES (20.00, '圍巾');
INSERT INTO `goods`(price,goods_name) VALUES (30.00, '圍巾');
INSERT INTO `goods`(price,goods_name) VALUES (40.00, '游戲機');
INSERT INTO `goods`(price,goods_name) VALUES (60.00, '皮包');
INSERT INTO `goods`(price,goods_name) VALUES (80.00, '游戲機');
INSERT INTO `goods`(price,goods_name) VALUES (220.00, '游戲機');
INSERT INTO `goods`(price,goods_name) VALUES (780.00, '圍巾');
INSERT INTO `goods`(price,goods_name) VALUES (560.00, '游戲機');
INSERT INTO `goods`(price,goods_name) VALUES (30.00, '皮包');
需求1: 以 商品名稱分組,把price字段的值在一行打印出來,分號分隔
select goods_name,group_concat(price) from goods group by goods_name;
需求2: 以 商品名稱分組,把price字段的值在一行打印出來,分號分隔 并且去除重復冗余的價格字段的值
select goods_name,group_concat(distinct price) from goods group by goods_name;
需求3: 以 商品名稱分組,把price字段的值在一行打印出來,分號分隔 去除重復冗余的價格字段的值 并且排序 從小到大
select goods_name,group_concat(distinct price order by price desc) from goods group by goods_name; #--錯誤的
select goods_name,group_concat(distinct price order by price+1 desc) from goods group by goods_name; #--正確的
#--注意以上存在隱式數據類型轉換 如果不這樣轉換排序出來的結果是錯誤的 , 因為我保存price價格的字段是varchar類型的
案例5
我們再結合group_concat()函數來做一個多表查詢的案例
準備 三張 測試數據表: 用戶表[user]、水果表[fruit]、用戶喜歡哪些水果的表[user_like]
首先 建立用戶表[user] SQL語句代碼如下
#-- user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT, #--id
`username` varchar(22) DEFAULT NULL, #--用戶名
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; #--設置表引擎 自動遞增起始值 默認編碼格式
#--插入測試數據
INSERT INTO `user`(username) VALUES ('張三');
INSERT INTO `user`(username) VALUES ('李四');
INSERT INTO `user`(username) VALUES ('王文玉');
建立水果表[fruit] SQL語句代碼如下
#-- fruit
CREATE TABLE `fruit` (
`id` int(11) NOT NULL AUTO_INCREMENT, #--id
`fruitname` varchar(22) DEFAULT NULL, #--水果名稱
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; #--設置表引擎 自動遞增起始值 默認編碼格式
#--插入測試數據
INSERT INTO `fruit`(fruitname) VALUES ('西瓜');
INSERT INTO `fruit`(fruitname) VALUES ('蘋果');
INSERT INTO `fruit`(fruitname) VALUES ('芒果');
INSERT INTO `fruit`(fruitname) VALUES ('梨');
INSERT INTO `fruit`(fruitname) VALUES ('葡萄');
建立 用戶喜愛表 [user_like]
但是建立這個表的時候 跟前面兩個表有所不同,小伙們們首先要搞清楚 這個表是一個什么用來干啥的表
分析清楚這個表的關系, 因為是用戶喜歡哪些水果的表 那么 一個水果可以被多個用戶所喜歡對吧? 反過來說一個用戶也可以喜歡多個水果吧 對吧 那么這里是一個什么關系呢 ?? 很明顯是一個 多對多的關系!
所以建立這個表 我們就可以使用 用戶的id 來對應 水果的id 就可以實現一個中間連接多對多的表了
SQL語句代碼如下:
#-- fruit
CREATE TABLE `user_like` (
`id` int(11) NOT NULL AUTO_INCREMENT, #--id
`user_id` int, #--用戶的id號
`fruit_id` int, #--水果的id號
CONSTRAINT user_like PRIMARY KEY (id,user_id,fruit_id) #--定義聯(lián)合主鍵 讓每一條記錄唯一
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; #--設置表引擎 自動遞增起始值 默認編碼格式
#--測試數據
INSERT INTO `user_like`(user_id,fruit_id) VALUES (1,1); #--這就代表用戶表中id號為1的用戶 喜歡fruit表中id號為1的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (1,2); #--這就代表用戶表中id號為1的用戶 喜歡fruit表中id號為2的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (1,3); #--這就代表用戶表中id號為1的用戶 喜歡fruit表中id號為3的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (2,3); #--這就代表用戶表中id號為2的用戶 喜歡fruit表中id號為3的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (2,4); #--這就代表用戶表中id號為2的用戶 喜歡fruit表中id號為4的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (2,5); #--這就代表用戶表中id號為2的用戶 喜歡fruit表中id號為5的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (3,5); #--這就代表用戶表中id號為3的用戶 喜歡fruit表中id號為5的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (3,1); #--這就代表用戶表中id號為3的用戶 喜歡fruit表中id號為1的水果
INSERT INTO `user_like`(user_id,fruit_id) VALUES (3,2); #--這就代表用戶表中id號為3的用戶 喜歡fruit表中id號為2的水果
#-- 以此類推...
好了 現在數據 和 表我們都已經準備好了 , 那么 接下來 我們就要開始進行 GROUP_CONCAT()函數的使用了
需求: 查出每個用戶喜歡的水果都有哪些!
#--查詢SQL如下
select u.username,group_concat(f.fruitname) from user_like as c inner join user as u on c.user_id=u.id inner join fruit as f on c.fruit_id=f.id group by c.user_id;
#--結果如下
+-----------+---------------------------+
| username | group_concat(f.fruitname) |
+-----------+---------------------------+
| 張三 | 芒果,蘋果 |
| 李四 | 梨,芒果,葡萄 |
| 王文玉 | 西瓜,葡萄,蘋果 |
+-----------+---------------------------+
博客園:https://www.cnblogs.com/leepandar/
CSDN: https://blog.csdn.net/Devilli0310
騰訊云:https://cloud.tencent.com/developer/column/78320
掘金:https://juejin.cn/user/1433418894949143