面试官问 MySQL 行转列?我把静态和动态两种方案都讲明白了

首页 2025-09-19 22:47:08

MySQL中的"行转列"是一个经典且非常实用的数据处理技巧,通常用于将数据从一种易于存储的格式转换为一种更易于阅读和报告的格式。下面我将详细解释其概念、方法和应用场景。

核心概念:什么是行转列?

  • 行数据:通常指数据库中最自然的存储格式,每一行代表一条独立的记录。

| 学生姓名 | 科目 | 成绩 |

| :------- | :--- | :--- |

| 张三 | 语文 | 90 |

| 张三 | 数学 | 85 |

| 李四 | 语文 | 95 |

| 李四 | 数学 | 70 |

  • 列数据(行转列后的目标):将某一列的唯一值(如​​科目​​​)转换为新的列名,并将其对应的值(如​​成绩​​)填充到新列下。

| 学生姓名 | 语文 | 数学 |

| :------- | :--- | :--- |

| 张三 | 90 | 85 |

| 李四 | 95 | 70 |

这种转换也称为数据透视(PIVOT)


实现行转列的几种方法

假设我们有上文的成绩表 ​​scores​​。

方法一:使用 ​​CASE WHEN​​ + 聚合函数(最通用、最灵活)

这是最经典和兼容性最好的方法,适用于几乎所有版本的 MySQL。

思路

  1. 使用 ​​GROUP BY​​​ 按唯一标识(如​​学生姓名​​)分组,确保一行一个学生。
  2. 对每个需要转换的列值,使用 ​​CASE WHEN​​​(或 ​​IF​​)语句判断并提取对应的值。
  3. 使用 ​​MAX​​​ 或 ​​MIN​​​、​​SUM​​ 等聚合函数对提取出的值进行聚合(通常用于确保每个分组下只有一个非空值)。

SQL 示例

SELECT

学生姓名,

MAX(CASE WHEN 科目 = '语文' THEN 成绩 ELSE NULL END) AS 语文,

MAX(CASE WHEN 科目 = '数学' THEN 成绩 ELSE NULL END) AS 数学

-- , ...可以继续添加其他科目

FROM scores

GROUP BY 学生姓名;

结果

| 学生姓名 | 语文 | 数学 |

| :------- | :--- | :--- |

| 张三 | 90 | 85 |

| 李四 | 95 | 70 |

为什么用 MAX

因为 ​​GROUP BY​​​ 后,每个学生的每门科目理论上只有一条记录。​​MAX​​​ 的作用是取回那条唯一记录的值。如果不用聚合函数,SQL 会报错。你也可以用 ​​MIN​​​ 或 ​​SUM​​,效果相同。

方法二:使用 ​​IF()​​ + 聚合函数(MySQL 的简洁写法)

​IF(condition, value_if_true, value_if_false)​​​ 是 ​​CASE WHEN​​ 的简化版,逻辑更清晰。

SQL 示例

SELECT

学生姓名,

MAX(IF(科目 = '语文', 成绩, NULL)) AS 语文,

MAX(IF(科目 = '数学', 成绩, NULL)) AS 数学

FROM scores

GROUP BY 学生姓名;

效果与方法一完全相同。

方法三:使用 ​​GROUP_CONCAT​​(另一种形式的“行转列”)

有时“转列”的目的不是生成多个新列,而是将多行数据合并到一个单元格中,用分隔符连接。这时 ​​GROUP_CONCAT​​ 非常有用。

SQL 示例

SELECT

学生姓名,

GROUP_CONCAT(科目, ':', 成绩 SEPARATOR '; ') AS 成绩单

FROM scores

GROUP BY 学生姓名;

结果

| 学生姓名 | 成绩单 |

| :------- | :----------------- |

| 张三 | 语文:90; 数学:85 |

| 李四 | 语文:95; 数学:70 |


高级应用:动态行转列

上面的方法有个巨大缺陷:需要手动编写每个要转换的列值(如‘语文’、‘数学’)。如果科目是不确定的(例如随时会增加‘英语’、‘物理’),上面的 SQL 就无法满足需求。

这时需要使用存储过程(Stored Procedure)动态生成 SQL 语句。

思路

  1. 查询出所有需要转换为列名的唯一值(如所有科目)。
  2. 使用字符串拼接函数(如 ​​CONCAT​​​, ​​GROUP_CONCAT​​​)构造出包含所有 ​​CASE WHEN​​ 语句的 SQL 字符串。
  3. 使用预处理语句(​​PREPARE​​​ & ​​EXECUTE​​)来执行这个动态生成的 SQL 字符串。

示例代码

-- 1. 定义变量存储动态生成的SQL

SET @sql = NULL;



-- 2. 查询所有不重复的科目,并拼接成 MAX(CASE WHEN...) 的格式

SELECT

GROUP_CONCAT(DISTINCT

CONCAT(

'MAX(IF(科目 = ''',

科目,

''', 成绩, NULL)) AS ',

CONCAT('`', 科目, '`') -- 用反引号包裹科目名,防止它是关键字

)

) INTO @sql

FROM scores;



-- 3. 拼接完整的SQL语句

SET @sql = CONCAT('SELECT 学生姓名, ', @sql, ' FROM scores GROUP BY 学生姓名');



-- 4. 预处理并执行动态SQL

PREPARE stmt FROM @sql;

EXECUTE stmt;

DEALLOCATE PREPARE stmt;

执行结果:无论科目如何变化,这段代码都会自动生成一个将所有科目作为列名的透视表。

| 学生姓名 | 语文 | 数学 | 英语 | (其他任何科目...) |

| :------- | :--- | :--- | :--- | :------------------ |

| 张三 | 90 | 85 | 92 | ... |

| 李四 | 95 | 70 | 88 | ... |


总结与选择

| 方法 | 适用场景 | 优点 | 缺点 |

| :----------------- | :--------------------------------------------- | :--------------------------- | :----------------------------- |

| ​CASE WHEN/IF​ | 要转换的列值是固定的、已知的(如科目固定) | 简单、直观、性能好 | 无法自动适应列值的变化 |

| ​GROUP_CONCAT

| 动态SQL | 要转换的列值是动态的、不确定的(如科目可变) | 全自动,一劳永逸 | 编写复杂,需要用到存储过程和预处理 |

核心要点

  1. 行转列的本质是条件判断和分组聚合
  2. 静态转换使用 ​​CASE WHEN​​​/​​IF​​​ + ​​MAX​​​ + ​​GROUP BY​​。
  3. 动态转换需要使用存储过程拼接 SQL,复杂度较高。
  4. 在选择方法前,一定要明确需求中的“列”是否是固定的。

另外搭配便捷的MYSQL备份工具,可定时备份、异地备份,MYSQL导出导入。可本地连接LINUX里的MYSQL,简单便捷。可以大大地提高工作效率喔。

MySQL连接就这么简单!本地远程、编程语言连接方法一网打尽
还在为MySQL日期计算头疼?这份加一天操作指南能解决90%问题
MySQL日志到底在哪里?Linux/Windows/macOS全平台查找方法在此
MySQL数据库管理工具全景评测:从Workbench到DBeaver的技术选型指南
MySQL密码忘了怎么办?这份重置指南能救急,Windows/Linux/Mac都适用
你的MySQL为什么经常卡死?可能是锁表在作怪!快速排查方法在此
MySQL单表卡爆怎么办?从策略到实战,一文掌握「分表」救命技巧
清空MySQL数据表千万别用错!DELETE和TRUNCATE这个区别可能导致重大事故
你的MySQL中文排序一团糟?记住这几点,轻松实现准确拼音排序!
别再混淆Hive和MySQL了!读懂它们的天壤之别,才算摸到大数据的门道