学生按大洲透视:行转列的标准套路
LeetCode 618 · 行转列、ROW_NUMBER、条件聚合
面试怎么回答
行转列我用「编号 + 条件聚合」:先 ROW_NUMBER() OVER (PARTITION BY continent ORDER BY name) 得到每个人在本列的行号,再 GROUP BY 行号,用 MAX(CASE WHEN continent = 'Asia' THEN name END) 生成各列。不需要预先知道哪个大洲人最多,人数不齐时自动补 NULL。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
题意
学生表有姓名和所属大洲(美洲、亚洲、欧洲)。输出一张透视表:每个大洲一列,每列里的学生按姓名字母顺序从上往下排,人数不够的位置留空。
表结构
- student:name、continent
思路
行转列的关键是先决定「每个人落在第几行」:按大洲分区、按姓名排序编号,编号就是行号。再按行号分组,用 MAX(IF(continent = 'America', name, NULL)) 这样的条件聚合把每个大洲的名字放到对应的列里。按行号分组还能自动处理各大洲人数不同的情况。
参考解法
写法 1:编号后条件聚合(推荐,不依赖哪个大洲人最多)
select
max(if(continent='America',name,null)) America,
max(if(continent='Asia',name,null)) Asia,
max(if(continent='Europe',name,null)) Europe
from
(select *, row_number() over(partition by continent order by name) rk
from student) t
group by rk
写法 2:三个子查询按行号关联(以美洲为主表,要求美洲人数最多)
select America,Asia,Europe
from(
select row_number() over(order by name) as rn,name as America from student
where continent='America'
) a
left join(
select row_number() over(order by name) as rn,name as Asia from student
where continent='Asia'
) b on a.rn=b.rn
left join(
select row_number() over(order by name) as rn,name as Europe from student
where continent='Europe'
) c on a.rn=c.rn
易错点
- 用 LEFT JOIN 拼三列时以哪个大洲为主表很关键:主表人数少,其他大洲多出来的人就丢了。条件聚合的写法没有这个问题。
- 用会话变量 @rownum 编号的老写法依赖执行顺序,MySQL 8 已不推荐。