SQL行转列 (及EAV模型获取数据)
参考文章:
http://www.williamsang.com/archives/1508.html
情景简介
学校里面记录成绩,每个人的选课不一样,而且以后会添加课程,所以不需要把所有课程当作列。数据库grade里面数据如下图,假定每个人姓名都不一样,作为主键。本文以MySQL为基础,其他数据库会有些许语法不同。
下面介绍三种方法:
方法一:
1
2
3
4
5
|
SELECT DISTINCT a. name , ( SELECT score FROM grade b WHERE a. name =b. name AND b.course= '语文' ) AS '语文' , ( SELECT score FROM grade b WHERE a. name =b. name AND b.course= '数学' ) AS '数学' , ( SELECT score FROM grade b WHERE a. name =b. name AND b.course= '英语' ) AS '英语' FROM grade a |
方法二:
1
2
3
4
5
|
SELECT name , SUM ( CASE course WHEN '语文' THEN score END ) AS '语文' , SUM ( CASE course WHEN '数学' THEN score END ) AS '数学' , SUM ( CASE course WHEN '英语' THEN score END ) AS '英语' FROM grade GROUP BY name |
方法三:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
DELIMITER && CREATE PROCEDURE sp_count() BEGIN #课程名称 DECLARE course_n VARCHAR (20); #所有课程数量 DECLARE count INT ; #计数器 DECLARE i INT DEFAULT 0; #拼接SQL字符串 SET @s = 'SELECT name' ; SET count = ( SELECT COUNT ( distinct course) FROM grade); WHILE i < count DO SET course_n = ( SELECT course FROM grade LIMIT i,1); SET @s = CONCAT(@s, ', SUM(CASE course WHEN ' , '\'' , course_n, '\'' , ' THEN score END )' , ' AS ' , '\'' ,course_n, '\'' ); SET i = i+1; END WHILE; SET @s = CONCAT(@s, ' FROM grade GROUP BY name' ); #用于调试 # SELECT @s; PREPARE stmt FROM @s; EXECUTE stmt; END && call sp_count(); |
方法分析:
第一种方法使用了表连接。
第二种使用了分组,对每个分组分别处理。
第三种使用了存储过程,其实是第二种方法的动态化,先计算出所有课程的数量,然后对每个分组进行课程查询。
很明显前两种方法属于硬编码,增加课程后就需要修改SQL。而第三种则没有这种问题。
Note:
MySQL中不能在一个存储过程中删除另一个存储过程,只能调用另一个存储过程
本来想在方法三里面写上:DROP PROCEDURE IF EXISTS sp_count();这是错误的。调试的时候如果写错了,只能手动删除了,也没找到好方法。
参考资料:
2013-8-8更新:
方法二还可以使用IF语句。
如下所示:
1
2
3
4
5
|
SELECT name , SUM (IF (course = '语文' , score , null ) ) as '语文' , SUM (IF (course = '数学' , score , null ) ) as '数学' , SUM (IF (course = '英语' , score , null ) ) as '英语 ' FROM grade GROUP BY name |
IF(expr1,expr2,expr3),如果expr1是TRUE(expr1<>0且expr1<>NULL),那么IF()返回expr2,否则它返回expr3。IF()返回一个数字或字符串值,取决于它被使用的上下
方法四:使用多次 自连接 (每多一行转列都多一次inner join)
SELECT `main_table`.*,`table_name`.`value` AS `name`,`table_name2`.`value` AS `url_path`
FROM `catalog_category_entity` AS `main_table`
INNER JOIN `catalog_category_entity_varchar` AS `table_name` ON `table_name`.`entity_id`=`main_table`.`entity_id`
INNER JOIN `catalog_category_entity_varchar` AS `table_name2` ON `table_name2`.`entity_id`=`main_table`.`entity_id`
WHERE 1=1
AND (`table_name`.`attribute_id` = (41))
AND (`table_name2`.`attribute_id` = (57))
AND (`main_table`.`parent_id` = (2))
GROUP BY main_table.entity_id