在MySQL中实现交叉表查询2(动态交叉表)
交叉表分为静态交叉表和动态交叉表。其中静态交叉表中的列是固定的,因此相对容易实现;而动态交叉表中的列需要动态生成。
一、静态交叉表的实现
参见上一篇文章:在MySQL中实现交叉表查询1(静态交叉表)
https://blog.csdn.net/weixin_44377973/article/details/103099573。
二、动态交叉表的实现
创建三张表:学生表、课程表、成绩表
create table student(
s_id char(10) comment '学号',
s_name char(20) not null default '' comment '姓名',
primary key(s_id)
);
create table course(
c_id char(5),
c_name char(20) not null default '' comment '课程名',
primary key(c_id)
);
create table score(
s_id char(10),
c_id char(5),
score int not null default 0,
primary key(s_id,c_id)
);
为以上三张表添加数据:
Insert Into student(s_id,s_name)
Values('2018001001','张小明'),('2018001002', '李小四'),('2018001003','赵小二'),
('2018001004','王小五'),('2018001005','刘长江'),('2018001006','周光明');
Insert Into course(c_id,c_name)
Values('C0001','数据库'),('C0002','数据结构'),('C0003','高等数学'),('C0004','市场营销'),
('C0005','电子商务'),('C0006','网络营销'),('C0007','物流管理');
Insert Into score(s_id,c_id,score)
Values('2018001001','C0001',88),('2018001002', 'C0001',76),('2018001003','C0001',62),
('2018001004','C0001',74),('2018001005','C0001',61),('2018001006','C0001',58),
('2018001001','C0002',66),('2018001002','C0002',54),('2018001003','C0002',72),
('2018001004','C0002',69),('2018001005','C0002',65),('2018001006','C0002',70),
('2018001001','C0003',83),('2018001002','C0003',68),('2018001003','C0003',85),
('2018001004','C0003',75),('2018001005','C0003',63),('2018001006','C0003',42),
('2018001001','C0004',28),('2018001002','C0004',99),('2018001003','C0004',92),
('2018001004','C0004',91),('2018001005','C0004',74),('2018001006','C0004',88),
('2018001001','C0005',77),('2018001002','C0005',66),('2018001003','C0005',70),
('2018001004','C0005',80),('2018001005','C0005',69),('2018001006','C0005',82),
('2018001001','C0006',64),('2018001002','C0006',62),('2018001003','C0006',66),
('2018001004','C0006',80),('2018001005','C0006',71),('2018001006','C0006',60);
第一步: 动态获取和静态交叉表相似的语句
select IFNULL(s_name,'总分') as '姓名',
sum(if(c_name='数据库',score,0)) as '数据库',sum(if(c_name='数据结构',score,0)) as '数据结构',
sum(if(c_name='高等数学',score,0)) as '高等数学',sum(if(c_name='市场营销',score,0)) as '市场营销',
sum(if(c_name='电子商务',score,0)) as '电子商务',sum(if(c_name='网络营销',score,0)) as '网络营销',
sum(score) as '总分'
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id
group by s_name
with rollup;
如果要动态生成以上语句,需要使用SQL语句拼接。命令如下:
select group_concat(distinct concat('sum(if(c.c_name=\'',c_name),'\',sc.score,0)) as \'',c.c_name,'\'')
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id
;
以上命令的查询结果为:
sum(if(c_name='市场营销',score,0)) as '市场营销',sum(if(c_name='数据库',score,0)) as '数据库',sum(if(c_name='数据结构',score,0)) as '数据结构',sum(if(c_name='电子商务',score,0)) as '电子商务',sum(if(c_name='网络营销',score,0)) as '网络营销',sum(if(c_name='高等数学',score,0)) as '高等数学'
这样就不用考虑具体的课程门数以及课程的名称,代码就可以固定下来。
第二步: 拼接完整的SQL语句
set @sql=null;
select group_concat(distinct concat('sum(if(c_name=\'',c_name),'\',score,0)) as \'',c_name,'\'')
into @sql
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id;
set @sql=concat('select IFNULL(s_name,\'总分\') as \'姓名\',',@sql,',sum(score) as \'总分\'
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id
group by s_name with rollup;');
prepare stmt from @sql;
execute stmt;
deallocate prepare stmt;
group_concat的用法请参见我的个人博客:MySQL中group_concat函数用法总结,查询结果如下:
mysql> execute stmt;
+-----------+--------------+-----------+--------------+--------------+--------------+--------------+--------+
| 姓名 | 市场营销 | 数据库 | 数据结构 | 电子商务 | 网络营销 | 高等数学 | 总分 |
+-----------+--------------+-----------+--------------+--------------+--------------+--------------+--------+
| 刘长江 | 74 | 61 | 65 | 69 | 71 | 63 | 403 |
| 周光明 | 88 | 58 | 70 | 82 | 60 | 42 | 400 |
| 张小明 | 28 | 88 | 66 | 77 | 64 | 83 | 406 |
| 李小四 | 99 | 76 | 54 | 66 | 62 | 68 | 425 |
| 王小五 | 91 | 74 | 69 | 80 | 80 | 75 | 469 |
| 赵小二 | 92 | 62 | 72 | 70 | 66 | 85 | 447 |
| 总分 | 472 | 419 | 396 | 444 | 403 | 416 | 2550 |
+-----------+--------------+-----------+--------------+--------------+--------------+--------------+--------+
7 rows in set (0.01 sec)
prepare语句语法说明:
prepare statement_name from SQL字符串;
excute statement_name; --执行预处理语句
deallocate | drop prepare statement_name; --删除定义
第三步:创建存储过程
如果直接在MySQL中操作,到第二步就可以了,但如果在JAVA或PHP等项目中使用,就会出现问题,此时需要创建存储过程。创建存储过程时只需要把第二步生成的代码直接放到存储过程的begin和end之间就可以了,代码如下:
drop procedure if exists sp_query_crosstable;
delimiter &&
create procedure sp_query_crosstable() reads SQL data
begin
set @sql=null;
select group_concat(distinct concat('sum(if(c_name=\'',c_name),'\',score,0)) as \'',c_name,'\'')
into @sql
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id;
set @sql=concat('select IFNULL(s_name,\'总分\') as \'姓名\',',@sql,',sum(score) as \'总分\'
from student s,course c,score sc where s.s_id=sc.s_id and c.c_id=sc.c_id
group by s_name with rollup;');
prepare stmt from @sql;
execute stmt;
deallocate prepare stmt;
end &&
delimiter ;
调用存储过程:
call sp_query_crosstable;