订阅更新

学生按大洲透视:行转列的标准套路

SQL 实战 123 题 · 第 42 题 困难 约 2 分钟读完

LeetCode 618 · 行转列、ROW_NUMBER、条件聚合

本文目录
  1. 面试怎么回答
  2. 题意
  3. 表结构
  4. 思路
  5. 参考解法
  6. 易错点

面试怎么回答

行转列我用「编号 + 条件聚合」:先 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 已不推荐。
刷题进度保存在当前浏览器