MySQL-行列转化

415 阅读3分钟

开发中遇到了行列转化的问题,在这里写出自己的实现方法,如果大家还有什么更好的方法,希望可以共同学习进步😁。

行转列,基础数据准备(表:student_row_to_column)

DROP TABLE IF EXISTS `student_row_to_column`;
CREATE TABLE `student_row_to_column` (
  `id` int NOT NULL,
  `t_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `course` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `score` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 
INSERT INTO `student_row_to_column` VALUES (1, '张三', '数学', 80);
INSERT INTO `student_row_to_column` VALUES (2, '张三', '语文', 79);
INSERT INTO `student_row_to_column` VALUES (3, '张三', '英语', 60);
INSERT INTO `student_row_to_column` VALUES (4, '李四', '数学', 88);
INSERT INTO `student_row_to_column` VALUES (5, '李四', '语文', 77);
INSERT INTO `student_row_to_column` VALUES (6, '李四', '英语', 66);
 

最终实现效果:

image-20241010144729546.png

方法一:case/if

  1. 要想实现行转列,需要先把每一行的数据,都添加为结果数据,这里就用到了 case / if (两种语句都可以)
   -- 分类(case)
SELECT t_name, 
   	CASE course WHEN '数学' THEN score ELSE NULL END as match_score,
   	CASE course WHEN '语文' THEN score ELSE NULL END as chinese_score,
   	CASE course WHEN '英语' THEN score ELSE NULL END as english_score
FROM student_row_to_column;
    
    -- 分类(if)
SELECT t_name, 
  MIN(IF(course='数学',score,NULL)) as match_score,
  MIN(IF(course='语文',score,NULL)) as chinese_score,
  MIN(IF(course='英语',score,NULL)) as english_score
FROM student_row_to_column;

运行效果如下:

image-20241010145103155.png

  1. 这时,问题就变得的简单了,我们只需要给数据进行分组就行,这里要注意,在分组的时候,要用到聚合函数
SELECT t_name, 
	MAX(CASE course WHEN '数学' THEN score ELSE NULL END) as match_score,
	MAX(CASE course WHEN '语文' THEN score ELSE NULL END) as chinese_score,
	MAX(CASE course WHEN '英语' THEN score ELSE NULL END) as english_score
FROM student_row_to_column
GROUP BY t_name;

效果如下:

image-20241010145359591.png

弊端:

当科目变化的时候(增加/减少),我们需要修改SQL语句。

方法二:group_concat

  1. 利用group_concat来展示, 这样展示的效果不够直观
SELECT
	t_name,
	GROUP_CONCAT( course SEPARATOR ',' ) AS course,
	GROUP_CONCAT( score SEPARATOR ',' ) AS score 
FROM
	student_row_to_column 
GROUP BY
	t_name;

效果:

image-20241010145802089.png

  1. 放到一起,这样看起来更方便
    SELECT
    	t_name,
    	GROUP_CONCAT( course, ':', score SEPARATOR ',' ) AS 'course:score' 
    FROM
    	student_row_to_column 
    GROUP BY
    	t_name;
    
    效果:

image-20241010145904646.png

弊端:

​ 虽然不会因为科目的多少而修改SQL,但是它展现的数据不能分开。

列转行,基础数据准备(表:student_column_to_row)

DROP TABLE IF EXISTS `student_column_to_row`;
CREATE TABLE `student_column_to_row` (
  `id` int NOT NULL,
  `t_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `match_score` int DEFAULT NULL,
  `chinese_score` int DEFAULT NULL,
  `english_score` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 
INSERT INTO `student_column_to_row` VALUES (1, '张三', 80, 79, 60);
INSERT INTO `student_column_to_row` VALUES (2, '李四', 88, 77, 66);

最终实现效果:

image-20241010150313640.png

方法一: union all

  1. 我们实现列转行,就是把多列数据拆分为多先,这里用到了 union all

    SELECT t_name, 'match_score' as course, match_score as score FROM student_column_to_row
    UNION ALL
    SELECT t_name, 'chinese_score' as course, chinese_score as score FROM student_column_to_row
    UNION ALL
    SELECT t_name, 'english_score' as course, english_score as score FROM student_column_to_row
    

    效果:

image-20241010150426903.png

  1. 之后,只需要整理一下数据,把姓名相同到的放到一起即可。这里需要在嵌套一层。

    SELECT * FROM (
    SELECT t_name, 'match_score' as course, match_score as score FROM student_column_to_row
    UNION ALL
    SELECT t_name, 'chinese_score' as course, chinese_score as score FROM student_column_to_row
    UNION ALL
    SELECT t_name, 'english_score' as course, english_score as score FROM student_column_to_row) t
    ORDER BY t.t_name ASC, t.course ASC
    

    效果:

image-20241010150549200.png

总结:

  • 行转列,用case/if
  • 列转先,用union all

大家还有什么方法,说出来一起进步一下吧😁。