锁定老帖子 主题:SQL经典模式--列转行
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-02-28
最后修改:2011-02-28
一般需要将列转成行来使用,一定是原有的Schema设计没有考虑周全。但是没有办法,为了保护现有的投资,不得不在糟糕的设计上周旋,用最小的代价去实现新需求。 毕竟认识都是由浅入深,为不健全的Schema设计付出代价,就像交税一样,无可避免。 举例: 课程表: 每门课程由5位老师教,要求包含老师的信息,以及一些课程的信息 create table cource (id int, name varchar(100), teacher1 int,teacher2 int,teacher3 int, teacher4 int, teacher5 int); insert into cource values (1,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (2,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (3,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (4,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (5,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (6,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (7,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (8,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (9,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (10,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (11,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); insert into cource values (12,concat('Course_',round(rand()*300)),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14),round(rand()*14)); 老师表: 记录了每个老师的年龄,级别,性别 create table teacher(id int, age int, level int, gender int); insert into teacher values (1, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (2, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (3, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (4, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (5, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (6, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (7, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (8, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (9, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (10, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (11, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (12, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (13, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); insert into teacher values (14, round(rand()*20+30), round(rand()*10), round(rand()*10)%2); 需求: 找出一些课程, 这些课程是由2位以上 男老师教,并且他们的级别大于3,并且他们年龄在40以下的。 一般过程性的方法: 先找出teacher表里面所有的teacherId (男老师教,并且他们的级别大于3,并且他们年龄在40),得到一个set 然后,把cource表加载到内存对象里面,然后开始循环,并用计数器去统计每个teacherId属性,看是否存在于set里面,如果存在就计数器+1, 计数器>3就跳出这条记录。 毫无疑问,以上的步骤还是比较的麻烦,估计一堆代码才理的清调理。 于是列转行的模式,就应运而生了。之所以称之为模式,是因为这样的问题场景实在是太常见了,就像在java里面要解决整个系统只用一个对象的问题而总结出了单例模式一样。 列转行需要一个工具表pivot,里面只有一列,存了1,2,3... , 你有多少个列需要转成行,就要多少个数。 我们这个例子是5 create table pivot (id int); insert into pivot values (1),(2),(3),(4),(5); 步骤一: 放大结果集,一条记录复制5条, 然后对与每条记录,根据pivot.id只取一个teacherId值,得到一个临时表 select c.id, c.name, case when p.id=1 then c.teacher1 when p.id=2 then c.teacher2 when p.id=3 then c.teacher3 when p.id=4 then c.teacher4 when p.id=5 then c.teacher5 else 0 end as teacherId from cource c, pivot p 步骤二: 在临时表的基础上,再进行过滤(男老师教,并且他们的级别大于2,并且他们年龄在40),得到合适的结果集 select tmp.name from ( select c.id, c.name, case when p.id=1 then c.teacher1 when p.id=2 then c.teacher2 when p.id=3 then c.teacher3 when p.id=4 then c.teacher4 when p.id=5 then c.teacher5 else 0 end as teacherId from cource c, pivot p ) tmp where tmp.teacherId in (select id from teacher where age<40 and gender=1 and level>3) 步骤三: 分组统计,课程是由3位以上符合要求老师教的 select tmp.name from ( select c.id, c.name, case when p.id=1 then c.teacher1 when p.id=2 then c.teacher2 when p.id=3 then c.teacher3 when p.id=4 then c.teacher4 when p.id=5 then c.teacher5 else 0 end as teacherId from cource c, pivot p ) tmp where tmp.teacherId in (select id from teacher where age<40 and gender=1 and level>3) group by tmp.name having count(*)>2 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-02-28
行列转换还有一个思路,就是建冗余表,而不是临时表,这是为了查询速度。
然后,每做什么修改,两个表都要改。 |
|
返回顶楼 | |
发表时间:2011-02-28
冗余表确实是个好办法,只是维护起来有点费力。
上面的子查询在执行计划中显示会开启一个临时表(Extra: Using temporary),如果需要转的记录很少,比如上面加上courceId<5,那么就无所谓了,只转一条,效率不会影响,冗余表提升空间相当小。 但如果要转的记录非常多,而且非常频繁,那么临时表的开销就是相当大(加载表相关记录的内容到临时表,并且是N*列数),由于临时表无法使用到索引,查询的速度会非常慢, 这时采用冗余的方式就非常好。 |
|
返回顶楼 | |
发表时间:2011-03-09
现在上班的公司面试题好像就有这个。。。TMD 还是要手写。。结果哥 悲剧了!!
|
|
返回顶楼 | |
发表时间:2011-03-09
冗余表。。物化视图
|
|
返回顶楼 | |
发表时间:2011-03-10
dolwenjian 写道 现在上班的公司面试题好像就有这个。。。TMD 还是要手写。。结果哥 悲剧了!!
悲剧了,就是你还是在那里上班了。 |
|
返回顶楼 | |
发表时间:2011-03-11
前一阵子去面试的一家公司就是考了这样的题目\不过还好\我答上来了
|
|
返回顶楼 | |
发表时间:2011-03-11
最后修改:2011-03-11
这个应该用的很多
|
|
返回顶楼 | |
发表时间:2011-03-16
交叉表的思路不行么?
|
|
返回顶楼 | |
发表时间:2011-03-18
首先更正LZ的一个英语单词的错误,课程不是cource 而是 Course。 还有对应建表语句,由于ORACLE 与 MYSQL的差别,现更正如下,(teache表也同样做相应的改动): create table course (id int, name varchar(100), teacher1 int,teacher2 int,teacher3 int, teacher4 int, teacher5 int); insert into course values (1,concat('Course_',round(sys.dbms_random.value(1,300))),round(sys.dbms_random.value(1,14)),round(sys.dbms_random.value(1,14)),round(sys.dbms_random.value(1,14)),round(sys.dbms_random.value(1,14)),round(sys.dbms_random.value(1,14))); |
|
返回顶楼 | |