SQL分组后单人多课程单行显示的执行原理及分步解析
课程查询SQL的两个问题解答
现有如下Courses表结构及初始化数据:
CREATE TABLE Courses (name VARCHAR(32), course VARCHAR(32), PRIMARY KEY(name, course)); INSERT INTO Courses VALUES('Tom', 'SQL'); INSERT INTO Courses VALUES('Tom', 'UNIX'); INSERT INTO Courses VALUES('Jack', 'SQL'); INSERT INTO Courses VALUES('Mike', 'SQL'); INSERT INTO Courses VALUES('Mike', 'Java'); INSERT INTO Courses VALUES('Jane', 'UNIX'); INSERT INTO Courses VALUES('Mary', 'SQL');
执行以下SQL可得到每个人的课程分布(每行对应一个人,各课程列显示是否选修):
select name, (select 'o' from Courses where name = c.name and course = 'SQL')as SQL, (select 'o' from Courses where name = c.name and course = 'UNIX')as UNIX, (select 'o' from Courses where name = c.name and course = 'Java')as Java from Courses c group by name
查询结果如下(每个人一行,对应课程列有值则表示选修该课程,无值则为NULL):
| name | SQL | UNIX | Java |
|---|---|---|---|
| Tom | o | o | NULL |
| Jack | o | NULL | NULL |
| Mike | o | NULL | o |
| Jane | NULL | o | NULL |
| Mary | o | NULL | NULL |
一、如何实现将每个人的所有课程合并显示在同一行?
根据需求不同,有两种常用实现思路:
思路1:行转列展示(分课程列显示)
这种是把每个人的课程拆分为多列,标记是否选修,有两种实现方式:
- 子查询方式:即题目中给出的SQL,为每个课程单独写关联子查询,判断当前用户是否选修该课程,返回
'o'或NULL,最后通过GROUP BY name合并同一用户的记录为一行。 - 条件聚合方式(更高效):用
CASE WHEN结合聚合函数(如MAX)实现,避免多次扫描表,性能更优,代码示例:
SELECT name, MAX(CASE WHEN course = 'SQL' THEN 'o' END) AS SQL, MAX(CASE WHEN course = 'UNIX' THEN 'o' END) AS UNIX, MAX(CASE WHEN course = 'Java' THEN 'o' END) AS Java FROM Courses GROUP BY name;
思路2:合并为单个字符串字段
如果需求是把一个人的所有课程放在同一个字段里,用字符串聚合函数(不同数据库语法略有差异):
- MySQL:
SELECT name, GROUP_CONCAT(course SEPARATOR ', ') AS courses FROM Courses GROUP BY name;
- PostgreSQL/SQL Server:
SELECT name, STRING_AGG(course, ', ') AS courses FROM Courses GROUP BY name;
结果示例:
| name | courses |
|---|---|
| Tom | SQL, UNIX |
| Jack | SQL |
| Mike | SQL, Java |
| Jane | UNIX |
| Mary | SQL |
二、给定SQL的具体分步执行逻辑
- 获取基础数据:
FROM Courses c扫描整个Courses表,得到所有name+course的原始记录。 - 按用户分组:
GROUP BY name对原始记录按name分组,每个用户仅保留一行数据(保证每个name唯一)。 - 执行关联子查询:对每个分组后的用户,分别执行三个子查询:
- 第一个子查询:在
Courses表中查找当前用户是否选修SQL,找到则返回'o',否则返回NULL。 - 第二个子查询:同理,判断当前用户是否选修
UNIX,返回对应标记或NULL。 - 第三个子查询:判断当前用户是否选修
Java,返回对应标记或NULL。
- 第一个子查询:在
- 组装最终结果:将
name与三个子查询的结果拼接成一行,每个用户对应一行,得到最终的行转列结果。
内容的提问来源于stack exchange,提问作者useryy
相关产品推荐
相关产品推荐

