用外连接进行行列转换(1):制作交叉表
drop table if exists course;
create table course(
name1 varchar(8),
course varchar(10)
);
insert into course values('赤井','SQL入门');
insert into course values('赤井','UNIX基础');
insert into course values('铃木','SQL入门');
insert into course values('工藤','SQL入门');
insert into course values('工藤','Java中级');
insert into course values('吉田','UNIX基础');
insert into course values('渡边','SQL入门');
-- 展开求交叉表:使用外连接
select distinct a.name1
, case when b.name1 is not null then '0' else null end 'SQL入门'
, case when c.name1 is not null then '0' else null end 'UNIX基础'
, case when d.name1 is not null then '0' else null end 'Java中级'
from course a left join course b on a.name1 = b.name1 and b.course = 'SQL入门'
left join course c on a.name1 = c.name1 and c.course = 'UNIX基础'
left join course d on a.name1 = d.name1 and d.course = 'Java中级'
;
-- 嵌套使用case表达式
select name1
, case when sum(case when course = 'SQL入门' then 1 end) = 1 then '0' end 'SQL入门'
, case when sum(case when course = 'UNIX基础' then 1 end) = 1 then '0' end 'UNIX基础'
, case when sum(case when course = 'Java中级' then 1 end) = 1 then '0' end 'Java中级'
from course
group by name1
;
结果图示:
外连接进行行列转换(2):汇总重复项于一列
drop table if exists personnel;
create table personnel(
employee varchar(8),
child_1 varchar(8),
child_2 varchar(8),
child_3 varchar(8)
);
insert into personnel values('赤井','一郎','二郎','三郎');
insert into personnel values('工藤','春子','夏子',NULL);
insert into personnel values('铃木','夏子',null,null);
insert into personnel values('吉田',null,null,null);
-- 列数据转换成行数据
select employee, child_1 as child from personnel
union all
select employee, child_2 as child from personnel
union all
select employee, child_3 as child from personnel
;
create view childern(child) as
select child_1 as child from personnel
union
select child_2 as child from personnel
union
select child_3 as child from personnel
;
select * from childern; -- 查看视图
-- 获取员工子女列表的SQL语句(没有孩子的员工也要输出)
select a.employee, b.child
from personnel a
left join childern b
on b.child in (a.child_1, a.child_2, a.child_3)
;
用视图加左连接,用in作为连接条件,重名也不会有影响。
生成完整的嵌套式表侧栏
在交叉表里制作嵌套式表侧栏
drop table if exists TblAge;
create table TblAge(
age_class int4,
age_range varchar(20)
);
insert into tblage values(1, '21岁~30岁');
insert into tblage values(2, '31岁~40岁');
insert into tblage values(3, '41岁~50岁');
drop table if exists Tblsex;
create table Tblsex(
sex_cd varchar(8),
sex varchar(8)
);
insert into tblsex values('m','男');
insert into tblsex values('f','女');
drop table if exists Tblpop;
create table Tblpop(
pref_name varchar(8),
age_class int4,
sex_cd varchar(8),
population integer
);
insert into Tblpop values('秋田',1,'m',400);
insert into Tblpop values('秋田',3,'m',1000);
insert into Tblpop values('秋田',1,'f',800);
insert into Tblpop values('秋田',3,'f',1000);
insert into Tblpop values('青森',1,'m',700);
insert into Tblpop values('青森',1,'f',500);
insert into Tblpop values('青森',3,'f',800);
insert into Tblpop values('东京',1,'m',900);
insert into Tblpop values('东京',1,'f',1500);
insert into Tblpop values('东京',3,'f',1200);
insert into Tblpop values('千叶',1,'m',900);
insert into Tblpop values('千叶',1,'f',1000);
insert into Tblpop values('千叶',3,'f',900);
-- 生成完整的嵌套式表侧栏
select m.age_class age_class
, m.sex_cd sex_cd
, t.pop_tohoku pop_tohoku
, t.pop_kanto pop_kanto
from (
select age_class, sex_cd
from tblage cross join tblsex
) m -- 使用交叉连接生成两张主表的笛卡尔积
left join
(
select age_class, sex_cd
, sum(case when pref_name in ('青森','秋田') then population end) pop_tohoku
, sum(case when pref_name in ('东京','千叶') then population end) pop_kanto
from tblpop
group by age_class, sex_cd
) t
on m.age_class = t.age_class
and m.sex_cd = t.sex_cd
;
生成嵌套式表侧栏时,如果先生成主表的笛卡尔积再进行连接,就很容易完成。
作为乘法运算的连接
drop table if exists items;
create table items(
item_no int4,
item varchar(8)
);
insert into items values(10, 'FD');
insert into items values(20, 'CD-R');
insert into items values(30, 'MO');
insert into items values(40, 'DVD');
drop table if exists saleshistory;
create table saleshistory(
sale_date date,
item_no int4,
quantity int8
);
insert into saleshistory values(20071001, 10, 4);
insert into saleshistory values(20071001, 20, 10);
insert into saleshistory values(20071001, 30, 3);
insert into saleshistory values(20071003, 10, 32);
insert into saleshistory values(20071003, 30, 12);
insert into saleshistory values(20071004, 20, 22);
insert into saleshistory values(20071004, 30, 7);
select i.item_no, t.total_qty
from items i
left join
(select item_no, sum(quantity) total_qty
from saleshistory
group by item_no ) t
on i.item_no = t.item_no
;
-- 先一对多的连接再聚合
select i.item_no, sum(t.quantity) total_qty
from items i left join saleshistory t
on i.item_no = t.item_no
group by i.item_no
;
一对一或者一对多关系的两个集合,在进行连接操作行数不会(异常)增加
全外连接
full outer join = left outer join UNION right
drop table if exists class_a;
create table class_a(
id int4,
name1 varchar(10)
);
insert into class_a values(1, '田中');
insert into class_a values(2, '铃木');
insert into class_a values(3, '伊集院');
drop table if exists class_b;
create table class_b(
id int4,
name1 varchar(10)
);
insert into class_b values(1, '田中');
insert into class_b values(2, '铃木');
insert into class_b values(4, '西园寺');
-- 全外连接保留全部信息 full outer join = left outer join UNION right
select a.id id
, a.name1 a_name
, b.name1 b_name
from class_a a left join class_b b on a.id = b.id
union
select b.id id
, a.name1 a_name
, b.name1 b_name
from class_a a right join class_b b on a.id = b.id
;
用外连接进行集合运算
-- 用外连接求差集:A-B
select a.id id, a.name1 name1
from class_a a left join class_b b
on a.id = b.id
where b.name1 is null
;
-- 用外连接求差集:B-A
select b.id id, b.name1 name1
from class_a a right join class_b b
on a.id = b.id
where a.name1 is null
;
练习
-- 1-5-1 先连接还是先聚合 -- 用一对多关系加聚合函数来 减少临时视图
select m.age_class age_class
, m.sex_cd sex_cd
, sum(case when pref_name in ('青森','秋田') then population end) pop_tohoku
, sum(case when pref_name in ('东京','千叶') then population end) pop_kanto
from (
select age_class, sex_cd
from tblage cross join tblsex
) m -- 使用交叉连接生成两张主表的笛卡尔积
left join tblpop t
on m.age_class = t.age_class
and m.sex_cd = t.sex_cd
group by m.age_class, m.sex_cd
;
-- 1-5-2 请留意孩子的人数
select a.employee, count(b.child) child_cnt
from personnel a
left join childern b
on b.child in (a.child_1, a.child_2, a.child_3)
group by a.employee
order by a.employee
;
-- 1-5-3 全连接和MERGE运算符
-- 将两张表的信息汇总到一张表上
select a.id
, case when a.name1 <> b.name1 or a.name1 is null then b.name1 else a.name1 end name_mix
from class_a a left join class_b b
on a.id = b.id
union
select b.id
, case when a.name1 <> b.name1 or a.name1 is null then b.name1 else a.name1 end name_mix
from class_a a right join class_b b
on a.id = b.id
;
mysql用merge合并表
/*
merge合并表的要求
1.合并的表使用的必须是MyISAM引擎
2.表的结构必须一致,包括索引、字段类型、引擎和字符集
*/
drop table if exists class_a;
create table class_a(
id int4,
name1 varchar(10)
)engine = MyISAM default charset = utf8 auto_increment = 1;
insert into class_a values(1, '田中');
insert into class_a values(2, '铃木');
insert into class_a values(3, '伊集院');
drop table if exists class_b;
create table class_b like class_a;
insert into class_b values(1, '田中');
insert into class_b values(2, '内海');
insert into class_b values(4, '伊集院');
drop table if exists class_ab;
create table class_ab(
id int4,
name1 varchar(10)
)engine = merge union(class_a, class_b) default charset = utf8 auto_increment = 1;
select * from class_ab;
除了便于同时引用多个数据表而无需发出多条查询,MERGE数据表还提供了以下一些便利。
- MERGE数据表可以用来创建一个尺寸超过各个MyISAM数据表所允许的最大长度逻辑单元
- 你看一把经过压缩的数据表包括到MERGE数据表里。比如说,在某一年结束之后,你应该不会再往相应的日志文件里添加记录,所以你可以用myisampack工具压缩它以节省空间,而MERGE数据表仍可以像往常那样工作
内容多来自 《SQL进阶教材》,仅做笔记。练习部分代码均为原创。