MySQL 行列转换
本文最后更新于:2020年12月30日 凌晨
场景
面试的时候遇到的一个问题,之前没有碰到过这种场景,所以却是无论如何都回答不了呢!然而本着遇到的坑跌倒过一次就够了的理念,回来时吾辈稍微 Google 了一下这个问题,结果便在此记录一下好啦
行转列
指的是将数据行根据状态区分为不同的列,主要应用场景应该是统计报表吧
例如下面这个 exam
表
1 |
|
直接查询会是下面这个样子
姓名 | 科目 | 分数 |
---|---|---|
琉璃 | 语文 | 90 |
琉璃 | 英语 | 85 |
楚轩 | 数学 | 100 |
楚轩 | 物理 | 100 |
张三 | 化学 | 40 |
李四 | 生物 | 100 |
然而需要的结果却是
姓名 | 语文 | 数学 | 英语 | 物理 | 化学 | 生物 |
---|---|---|---|---|---|---|
张三 | 0 | 0 | 0 | 0 | 40 | 0 |
李四 | 0 | 0 | 0 | 100 | 0 | 0 |
楚轩 | 0 | 100 | 0 | 100 | 0 | 0 |
琉璃 | 90 | 0 | 85 | 0 | 0 | 0 |
大致的实现思路是判断 subject
的值,如果等于 转换列
的值,就将之设置为该 转换列
的值。(此处的 转换列
指的是根据 subject
的值查询的新列)
目前网络上能找到的方法有下面两种
使用 if 实现行转列
1 |
|
优点:简单方便,即便是将几列合并也可以简单做到。例如我们想要统计主科/副科的总分
1 |
|
查询结果
姓名 | 主科 | 副科 |
---|---|---|
张三 | 0 | 40 |
李四 | 0 | 100 |
楚轩 | 100 | 100 |
琉璃 | 250 | 0 |
或者简单的实现小计
1 |
|
查询结果
姓名 | 语文 | 数学 | 英语 | 物理 | 化学 | 生物 | total |
---|---|---|---|---|---|---|---|
张三 | 0 | 0 | 0 | 0 | 40 | 0 | 40 |
李四 | 0 | 0 | 0 | 0 | 0 | 100 | 100 |
楚轩 | 0 | 100 | 0 | 100 | 0 | 0 | 200 |
琉璃 | 90 | 0 | 85 | 0 | 0 | 0 | 250 |
total | 90 | 100 | 85 | 100 | 40 | 100 | 590 |
使用 case when 实现行转列
1 |
|
优点:相比于 if
更加灵活,可以对每个 转换列
的值进行单独的处理。例如我们想要统计主科/副科的总分,并设置计算语文/数学时增加一半,而英语的分数则忽略不计
感觉这个优势相当的小,当然如果用到的话却是无需多言的
1 |
|
查询结果
姓名 | 主科 | 副科 |
---|---|---|
张三 | 0.0 | 40 |
李四 | 0.0 | 100 |
楚轩 | 150.0 | 100 |
琉璃 | 247.5 | 0 |
使用子查询实现行转列
1 |
|
优点:使用起来最灵活,但代码量也是最大的。可以对每一个列的多条/单条数据进行单独的处理,不需要必须使用统计函数(sum/avg/max/min/count
)。例如上面就是如果查到了多条数据就直接取第一条,当然也可以对第一条数据做后续处理。
使用 group_concat 简单的行连接
并非是真正的行转列,实际上只是把不同字段的数据 连接 了起来
1 |
|
查询结果
姓名 | 成绩单 |
---|---|
张三 | 化学 40 |
李四 | 生物 100 |
楚轩 | 数学 100,物理 100 |
琉璃 | 语文 75,语文 90,英语 85 |
列转行
将类似的列按照某种规则变成一列,并生成等同倍数的行。
我们需要将上面行转列得到的表转换回来,例如下面的 exam_score
表
1 |
|
直接查询结果是
name | language | mathematics | english | physical | chemistry | biological |
---|---|---|---|---|---|---|
张三 | 0 | 0 | 0 | 0 | 40 | 0 |
李四 | 0 | 0 | 0 | 0 | 0 | 100 |
楚轩 | 0 | 100 | 0 | 100 | 0 | 0 |
琉璃 | 90 | 0 | 85 | 0 | 0 | 0 |
然而我们需要得到
姓名 | 科目 | 分数 |
---|---|---|
琉璃 | 语文 | 90 |
琉璃 | 英语 | 85 |
楚轩 | 数学 | 100 |
楚轩 | 物理 | 100 |
张三 | 化学 | 40 |
李四 | 生物 | 100 |
使用 union all 联合查询
1 |
|
唔,好长的 sql 语句,这还只是 6 个 转换列
,如果有更多的话恐怕。。。
总结
sql 行转列的问题
sql 的技巧确实很多,然而相比之下 sql 只是一门 结构化查询语言,并不算是真正的编程语言呢!行转列/列转行这些需求放到真正的编程语言中是很容易处理的,下面演示使用 js 的实现
使用 JavaScript 实现行转列
假设有下面这样一个 json 数据
1 |
|
转换方法
1 |
|
看起来好像更长了?但实际上 groupBy()/uniqueBy()
都是通用的函数,所以实际代码应该不超过 20 行。转换后的数据如下
1 |
|
使用 JavaScript 实现列转行
那么,如何转换回来呢?转换回来的话却是简单许多了呢
1 |
|
那么,关于 MySQL 行列转换的问题就到这里啦