取经四人组SQL

一.表结构与数据

  • 1.create table user1 (id int not null,user_name varchar(20) not null,over varchar(20) default null) comment '师徒四人表';
  • 2.create table user2 (id int not null,user_name varchar(20) not null,over varchar(20) default null) comment '悟空兄弟表';
  • 3.插入数据
insert into user1 (id,user_name) values (1,'唐僧'),(2,'猪八戒'),(3,'孙悟空'),(4,'沙僧');
insert into user2 (id,user_name) values (1,'孙悟空'),(2,'牛魔王'),(3,'蛟魔王'),(4,'鹏魔王'),(5,'狮驼王');
  • 4.更新字段
update user1 set over='旃檀功德佛' where user_name='唐僧';
update user1 set over='净坛使者' where user_name='猪八戒';
update user1 set over='斗战神佛' where user_name='孙悟空';
update user1 set over='金身罗汉' where user_name='沙僧';
  • 5.更新字段
update user2 set over='成佛' where user_name='孙悟空';
update user2 set over='被降服' where user_name='牛魔王';
update user2 set over='被降服' where user_name='蛟魔王';
update user2 set over='被降服' where user_name='鹏魔王';
update user2 set over='被降服' where user_name='狮驼王';
  • 6.师徒杀怪表
create table user_kills (id int not null,user_id int  not null, timestr DATETIME not null,kils int not null) comment '师徒杀怪表';
insert into user_kills values(1,2,'2013-01-10 00:00:00',10);
insert into user_kills values(2,2,'2013-02-01 00:00:00',2);
insert into user_kills values(3,2,'2013-02-05 00:00:00',12);
insert into user_kills values(4,4,'2013-01-10 00:00:00',3);
insert into user_kills values(5,4,'2013-02-11 00:00:00',5);
insert into user_kills values(6,4,'2013-02-06 00:00:00',1);
insert into user_kills values(7,3,'2013-01-11 00:00:00',20);
insert into user_kills values(8,3,'2013-02-12 00:00:00',10);
insert into user_kills values(9,3,'2013-02-07 00:00:00',17);
  • 7.修正字段
alter table user_kills change kils kills int; 

二.连接的几种方式

  • 1.inner join,基于连接谓词将两张表(A表,B表)的列组合在一起,产生新的结果表
    image
select colname from table a inner join table b on a.key=b.key;
  • 需求:查询A/B表中的公共部分,查询同时存在user1和user2的用户信息
select user1.user_name,user1.over,user2.over from user1,user2 where user1.user_name=user2.user_name;
select a.user_name,a.over,b.over from user1 a inner join user2 b where a.user_name=b.user_name;
select a.user_name,a.over,b.over from user1 a inner join user2 b on a.user_name = b.user_name;
select a.user_name,a.over,b.over from user1 a,user2 b where a.user_name=b.user_name and a.user_name in (select user_name from user2);
select a.user_name,a.over,b.over from user1 a left join user2 b on a.user_name=b.user_name where a.user_name in (select user_name from user2);
select a.user_name,a.over,b.over from user1 a left join user2 b on a.user_name=b.user_name where b.user_name is not null;
  • 2.left join,以左边的表(A/B)为基准,将A表所有结果集显示
    image
select colname from table a left join table b on a.key=b.key;

image

select colname from table a left join table b where a.key=b.key where b.key is null;
  • 需求:只显示存在user1不存在user2的结果集

可以使用in,但是in是不走索引的

select a.user_name,a.over,b.over from user1 a left join user2 b on a.user_name=b.user_name where a.user_name not in (select user_name from user2);
select a.user_name,a.over,b.over from user1 a left join user2 b on a.user_name=b.user_name where b.user_name is null;
  • 3.right join,以右边的表(A/B)为基准,将B表所有结果集显示
    image
select colname from table a right join table b on a.key=b.key;

image

elect colname from table a right join table b on a.key=b.key where a.key is null;
  • 需求:只显示只存在user2不存在user1的结果集
select b.user_name,a.over,b.over from user1 a right join user2 b on a.user_name=b.user_name where b.user_name not in (select user_name from user1);
select b.user_name,a.over,b.over from user1 a right join user2 b on a.user_name=b.user_name where a.user_name is null;
  • 4.full outer,取并集或者并集的异
    image
    并集:select colname from table a full join table b on a.key=b.key;
    异:select colname from table a full join table b on a.key=b.key where a.key is null or b.key is null;
select * from user1 a full join user2 b on a.user_name=b.user_name;
mysql不支持全连接,MSSQL和oracle支持
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'full join user2 b on a.user_name=b.user_name' at line 1
  • 使用union all,先左连接后右连接再取并集
select a.user_name,a.over,b.over from user1 a left join user2 b on a.user_name=b.user_name
union all
select b.user_name,b.over,a.over from user1 a right join user2 b on a.user_name=b.user_name;

union和union all
1)必须具有相同列数,和能转换的列字段类型
2)union去重进行默认排序
3)union包含重复行,不排序

  • 5.corss,笛卡尔积,N行*M行结果集
    select * from user1 a cross join user2 b;

三.行列转换

1.行转列

应用场景:报表统计
image
image

  • 需求:求杀怪总数-行转列例子:
    image
    1)第一步:先求每个人杀怪数量
select a.user_name,b.kills from user1 a join user_kills b on a.id=b.user_id;

2)第二步:再分组汇总成行

select a.user_name,sum(kills) from user1 a join user_kills b on a.id=b.user_id group by a.user_name;

3)第三步:取聚合函数别名

select a.user_name,sum(c.kills) from user1 a join user_kills c on a.id=c.user_id and a.user_name='孙悟空';

4)第四步:最后行转列

select sum(c.kills) as '孙悟空' from user1 a join user_kills c on a.id=c.user_id and a.user_name='孙悟空';

5)第五步:1行乘以1行还是1行,数量越大花费成本越大,不常用

select * from (
select sum(kills) as '孙悟空' from user1 a join user_kills c on a.id=c.user_id and a.user_name='孙悟空') 
x cross join
(select sum(kills) as '猪八戒' from user1 a join user_kills c on a.id=c.user_id and a.user_name='猪八戒') 
y cross join 
(select sum(kills) as '沙僧' from user1 a join user_kills c on a.id=c.user_id and a.user_name='沙僧') z;
  • 更通用的是case语句
case when colname1 then colname2 else xxx end 当满足colname1条件,返回coname2并结束
select sum(case when user_name='孙悟空' then kills end) as '孙悟空',sum(case when user_name='猪八戒' then kills end ) as  '猪八戒',sum(case when user_name='沙僧' then kills end) as '沙僧' from user1 a join user_kills b on a.id=b.user_id;
2.另一个行转列

应用场景:1)属性拆分
image
应用场景:2)ETL数据导入导出

  • 需求:将user1中的每个人的电话转成行
    image
    增加数据
alter table user1 add column mobile varchar(100);
update user1 set mobile='121123456,141123456,151123456' where id=1;
update user1 set mobile='12144643321,14144643321' where id=2;
update user1 set mobile='12146666666,14166666666,16166666666,18166666666' where id=3;
update user1 set mobile='12198765432,14198765432' where id=4;

原表数据全部在一列中显示:

root@master 14:53:  [db1]> select * from user1;
+----+-----------+-----------------+-------------------------------------------------+
| id | user_name | over            | mobile                                          |
+----+-----------+-----------------+-------------------------------------------------+
|  1 | 唐僧      | 旃檀功德佛      | 121123456,141123456,151123456                   |
|  2 | 猪八戒    | 净坛使者        | 12144643321,14144643321                         |
|  3 | 孙悟空    | 斗战神佛        | 12146666666,14166666666,16166666666,18166666666 |
|  4 | 沙僧      | 金身罗汉        | 12198765432,14198765432                         |
+----+-----------+-----------------+-------------------------------------------------+
4 rows in set (0.00 sec)
  • 方法:引用序列表:只存序列号的表,就是单列多行表
create table tb_sequence(id int auto_increment not null,primary key(id));
insert into tb_sequence values(),(),(),(),(),(),(),(),();
#1个括号代表插入1个序列

1)第一步:增加逗号,计算号码个数,几个逗号就几个号码

select user_name,concat(mobile,',') as mobile,length(mobile)-length(replace(mobile, ',' , ''))+1 size from user1 b;

concat(mobile,',') 字符连接,给mobile字段的值后面增加1个逗号
length(replace(mobile, ',' , '') 将出现的,号全部剪掉
replace(object,search,replace) 把object中出现search的全部替换为replace
length(mobile)-length(replace(mobile, ',' , ''))+1,有逗号的长度减去没有逗号的长度,求个数

2)第二步:根据个数交叉连接序列表,生成多行的结果集

select * 
from tb_sequence a 
cross join (
select user_name,concat(mobile,',') as mobile,length(mobile)-length(replace(mobile, ',' , ''))+1 size from user1 b)
b on a.id <= b.size;

3)第三步:

select user_name,replace(substring(substring_index(mobile,',',a.id),char_length(substring_index(mobile,',',a.id-1))+1),',','') as mobile 
from tb_sequence a 
cross join (
select user_name,concat(mobile,',') as mobile,length(mobile)-length(replace(mobile, ',' , ''))+1 size from user1 b)
b on a.id <= b.size;
3.列转行

image
增加数据:

create table user1_equipment ( id int, user_id int,arms varchar(10),clothing varchar(10),shoe varchar(10));
insert into user1_equipment values(1,3,'金箍棒','锁子黄金甲','藕丝步云履');
insert into user1_equipment values(2,2,'九齿钉耙','僧衣','僧鞋');
insert into user1_equipment values(3,4,'降妖宝杖','僧衣','僧鞋');
insert into user1_equipment values(4,1,'九环锡杖','锦斓袈裟','僧鞋');
  • 第一种方法:
    基础表
select a.user_name,arms,clothing,shoe from user1 a join user1_equipment b on a.id=b.user_id; 
+-----------+--------------+-----------------+-----------------+
| user_name | arms         | clothing        | shoe            |
+-----------+--------------+-----------------+-----------------+
| 孙悟空    | 金箍棒       | 锁子黄金甲      | 藕丝步云履      |
| 猪八戒    | 九齿钉耙     | 僧衣            | 僧鞋            |
| 沙僧      | 降妖宝杖     | 僧衣            | 僧鞋            |
| 唐僧      | 九环锡杖     | 锦斓袈裟        | 僧鞋            |
+-----------+--------------+-----------------+-----------------+
4 rows in set (0.00 sec)

1)将一列转成行

select a.user_name,'arms' as equipment,arms from user1 a join user1_equipment b on a.id=b.user_id; 
select a.user_name,'clothing' as equipment,clothing from user1 a join user1_equipment b on a.id=b.user_id; 
select a.user_name,'shoe' as equipment,shoe from user1 a join user1_equipment b on a.id=b.user_id; 

2)连接不同的结果集用union

select a.user_name,'arms' as equipment,arms from user1 a join user1_equipment b on a.id=b.user_id
union all
select a.user_name,'clothing' as equipment,clothing from user1 a join user1_equipment b on a.id=b.user_id
union all
select a.user_name,'shoe' as equipment,shoe from user1 a join user1_equipment b on a.id=b.user_id
order by user_name;
  • 第二种方法:
    使用序列号实现行转列
    基础表
select a.user_name,arms,clothing,shoe from user1 a join user1_equipment b on a.id=b.user_id; 
+-----------+--------------+-----------------+-----------------+
| user_name | arms         | clothing        | shoe            |
+-----------+--------------+-----------------+-----------------+
| 孙悟空    | 金箍棒       | 锁子黄金甲      | 藕丝步云履      |
| 猪八戒    | 九齿钉耙     | 僧衣            | 僧鞋            |
| 沙僧      | 降妖宝杖     | 僧衣            | 僧鞋            |
| 唐僧      | 九环锡杖     | 锦斓袈裟        | 僧鞋            |
+-----------+--------------+-----------------+-----------------+
4 rows in set (0.00 sec)

1)需要将1行转换成3行

select user_name,arms,clothing,shoe from user1 a join user1_equipment b on a.id=b.user_id 
cross join
tb_sequence c where c.id <= 3 order by user_name;

2)使用case只取一次值

select user_name,
case when c.id=1 then arms end,
case when c.id=2 then clothing end,
case when c.id=3 then shoe end
from user1 a join user1_equipment b on a.id=b.user_id 
cross join
tb_sequence c where c.id <= 3 order by user_name;

3)使用coalesce取非空值

select user_name,
coalesce(case when c.id=1 then arms end,
case when c.id=2 then clothing end,
case when c.id=3 then shoe end) as equiment
from user1 a join user1_equipment b on a.id=b.user_id 
cross join
tb_sequence c where c.id <= 3 order by user_name;

4)增加额外列

select user_name,
case 
when c.id=1 then 'arms'
when c.id=2 then 'clothing'
when c.id=3 then 'shoe'
end as equiment,
coalesce(case when c.id=1 then arms end,
case when c.id=2 then clothing end,
case when c.id=3 then shoe end) as equiment
from user1 a join user1_equipment b on a.id=b.user_id 
cross join
tb_sequence c where c.id <= 3 order by user_name;

三.生成唯一序列号

  • 1.数据库主键
    业务序列号 发票号 车票号 订单号
    image
    优先选择系统提供的序列号生成方式
    create table t (id int auto_increment not null, primary key(id));
    可能会产生跳号问题

  • 2.特殊情况下可以使用sql方式生产序列号abc123abc
    需求:生成订单号,并且订单号的格式如下
    YYYYMMDDNNNNNNNN:201505120000003
    image

四.删除重复数据

人为:重复录入,重复提交
系统:升级、设计,可以重复的时候不可以使用

  • 如何查询数据是否重复--利用group by和having
create table user1_test( id int auto_increment not null,
user_name varchar(3),
 over varchar(5),
 mobile varchar(100),
 primary key(id));
insert into user1_test(user_name,over,mobile) select user_name,over,mobile from user1;
insert into user1_test(user_name,over,mobile) from user1 limit 2;
select user_name,count(*) from user1_test group by user_name having count(*) >1;
select user_name,over,count(*) from user1_test group by user_name,over having count(*) >1;

删除重复数据--对于相同数据保留ID最大者

delete a
from user1_test a join (
select user_name,count(*) ,max(id) as id
from user1_test
group by user_name having count(*) > 1)
b on a.user_name=b.user_name
where a.id < b.id
  • 如何删除上表的重复值?
    image
    列转行,删除重复值

五.如何在子查询中匹配两个值

子查询:当一个查询是另一个查询的条件时,称之为子查询
场景:
尽量使用连接替代子查询,子查询某些情况下查询速度并不好

1.使用子查询可以避免由于子查询中的数据产生的重复
  • 需求:谁在取经路上打了怪
select user_name from user1 where id in (select user_id from user_kills);
user_id 在user_kills中是重复出现,但是整体结果集并没有重复

改成使用连接方式

select a.user_name from user1 a join user_kills b on a.id=b.user_id;
#连接方式可能会存在1对多或者多对多情况,需要去重:
select distinct a.user_name from user1 a join user_kills b on a.id=b.user_id;
2.使用子查询更符合语义,更好理解
  • 需求:查询出每一个取经人打怪最多的日期,并列出取经人的姓名,打怪最多的日期和打怪数量
  • 1)先求最大的打怪数量
select user_id,max(kills) cnt from user_kills group by user_id; 
  • 2)根据打怪数量过来日期
select a.user_name,b.timestr,kills
from user1 a join user_kills b on a.id=b.user_id
join (select user_id,max(kills) as cnt from user_kills group by user_id) 
c on b.user_id=c.user_id and b.kills=c.cnt
  • 改成子查询方式:mysql独有的多列过滤方式
select a.user_name,b.timestr,kills from user1 a
join user_kills b
on a.id=b.user_id
where (b.user_id,b.kills ) in(
select user_id,max(kills)
from user_kills
group by user_id);

六.解决同属性多过滤的问题

create table user1_skills (user_name varchar(20),skill varchar(20),skill_level int);
alter table user1_skills change user_name user_id int;
insert into user1_skills values(1,'紧箍咒',5);
insert into user1_skills values(1,'打坐',4);
insert into user1_skills values(1,'念经',5);
insert into user1_skills values(1,'变化',0);
#
insert into user1_skills values(2,'变化',4);
insert into user1_skills values(2,'腾云',3);
insert into user1_skills values(2,'浮水',5);
insert into user1_skills values(2,'念经',0);
insert into user1_skills values(2,'紧箍咒',0);
#
insert into user1_skills values(3,'变化',5);
insert into user1_skills values(3,'腾云',5);
insert into user1_skills values(3,'浮水',3);
insert into user1_skills values(3,'念经',2);
insert into user1_skills values(3,'请神',5);
insert into user1_skills values(3,'紧箍咒',0);
#
insert into user1_skills values(4,'变化',2);
insert into user1_skills values(4,'腾云',2);
insert into user1_skills values(4,'浮水',4);
insert into user1_skills values(4,'念经',1);
insert into user1_skills values(4,'紧箍咒',0);
  • 需求:查询出同时具有变化和念经两项技能的取经人
    1.不能得出结果的SQL:
select a.user_name,b.skill,b.skill_level from user1 a join user1_skills b on a.id=b.user_id where  b.skill in('变化','念经') and b.skill_level > 0;
in是or的判断,因此只要符合条件之一就出结果
select a.user_name,b.skill,b.skill_level from user1 a join user1_skills b on a.id=b.user_id where ( b.skill ='变化' or b.skill ='念经' ) and b.skill_level > 0;

2.两次关联同一张表

select a.user_name,b.skill,c.skill 
from user1 a 
join user1_skills b 
on a.id=b.user_id  and b.skill='变化' 
join user1_skills c 
on a.id=c.user_id and c.skill='念经' 
where c.skill_level >0  and b.skill_level >0 ;
#
select a.user_name,b.skill,c.skill 
from user1 a 
join user1_skills b 
on a.id=b.user_id   
join user1_skills c 
on a.id=c.user_id and c.skill='念经' 
where b.skill='变化'  and c.skill='念经'  and c.skill_level >0  and b.skill_level >0 ;
  • 需求:查询出同时具有变化和念经、腾云三项技能的取经人
select a.user_name,b.skill,c.skill,d.skill
from user1 a 
join user1_skills b 
on a.id=b.user_id   
join user1_skills c 
on a.id=c.user_id 
join user1_skills d
on a.id=d.user_id
where d.skill='腾云' and b.skill='变化'  and c.skill='念经'  and c.skill_level >0  and b.skill_level >0 and d.skill_level >0 ;
#
select a.user_name,b.skill,c.skill,d.skill
from user1 a 
join user1_skills b 
on a.id=b.user_id   and b.skill='变化'
join user1_skills c  
on a.id=c.user_id and c.skill='念经' 
join user1_skills d
on a.id=d.user_id and d.skill='腾云'  
where  c.skill_level >0  and b.skill_level >0 and d.skill_level >0 ;
#
select a.user_name,b.skill,c.skill,d.skill
from user1 a 
join user1_skills b 
on a.id=b.user_id   and b.skill='变化' and b.skill_level >0
join user1_skills c  
on a.id=c.user_id and c.skill='念经'  and c.skill_level >0
join user1_skills d
on a.id=d.user_id and d.skill='腾云'   and d.skill_level >0 ;
  • 需求:在100个技能查询出同时具有变化和念经、腾云、浮水四项技能的取经人?
select a.user_name,b.skill,c.skill,d.skill,e.skill
from user1 a 
join user1_skills b 
on a.id=b.user_id   and b.skill='变化' and b.skill_level >0
join user1_skills c  
on a.id=c.user_id and c.skill='念经'  and c.skill_level >0
join user1_skills d
on a.id=d.user_id and d.skill='腾云'   and d.skill_level >0 
join user1_skills e
on a.id=e.user_id and e.skill='浮水'   and e.skill_level >0 ;
#
#left join会有null值:
select a.user_name,b.skill,c.skill,d.skill,e.skill
from user1 a 
left join user1_skills b 
on a.id=b.user_id   and b.skill='变化' and b.skill_level >0
left join user1_skills c  
on a.id=c.user_id and c.skill='念经'  and c.skill_level >0
left join user1_skills d
on a.id=d.user_id and d.skill='腾云'   and d.skill_level >0 
left join user1_skills e
on a.id=e.user_id and e.skill='浮水'   and e.skill_level >0 ;
  • 需求:在100个技能查询出同时具有变化和念经、腾云、浮水任意两项技能的取经人?
select a.user_name,b.skill,c.skill,d.skill,e.skill
from user1 a 
left join user1_skills b 
on a.id=b.user_id   and b.skill='变化' and b.skill_level >0
left join user1_skills c  
on a.id=c.user_id and c.skill='念经'  and c.skill_level >0
left join user1_skills d
on a.id=d.user_id and d.skill='腾云'   and d.skill_level >0 
left join user1_skills e
on a.id=e.user_id and e.skill='浮水'   and e.skill_level >0 
where (case when b.skill is not null then 1 else 0 end) +
(case when c.skill is not null then 1 else 0 end) +
(case when d.skill is not null then 1 else 0 end) +
(case when e.skill is not null then 1 else 0 end) >= 2;
  • 实际上只需要user_name
select a.user_name
from user1 a 
left join user1_skills b 
on a.id=b.user_id   and b.skill='变化' and b.skill_level >0
left join user1_skills c  
on a.id=c.user_id and c.skill='念经'  and c.skill_level >0
left join user1_skills d
on a.id=d.user_id and d.skill='腾云'   and d.skill_level >0 
left join user1_skills e
on a.id=e.user_id and e.skill='浮水'   and e.skill_level >0 
where (case when b.skill is not null then 1 else 0 end) +
(case when c.skill is not null then 1 else 0 end) +
(case when d.skill is not null then 1 else 0 end) +
(case when e.skill is not null then 1 else 0 end) >= 2;
  • 使用group by过滤同一属性多值的情况更通用:
select a.user_name
from user1 a
join user1_skills b on a.id=b.user_id
where b.skill in('念经','变化','腾云','浮水') and b.skill_level > 0
group by
a.user_name having count(*) >=2 ;

七.如何计算累进税问题

image

alter table user1 add column money float(10,2);
update user1 set money=35000.00 where id=1;
update user1 set money=15000.00 where id=2;
update user1 set money=28000.00 where id=3;
update user1 set money=8000.00 where id=4;
create table taxRate(id int not null auto_increment primary key,low float(10,2) not null,high float(10,2) not null,rate float(2,2) not null);
insert into taxRate(low,high,rate) values
(0.00,1500.00.0.03),
(1500.00,4500.00,0.1),
(4500.00,9000.00,0.2),
(9000.00,35000.00,0.25),
(35000.00,55000.00,0.3),
(55000.00,80000.00,0.35),
(80000.00,9999999.00,0.45);
  • 需求:每个取经人缴税金额是多少?
    1)使用join实现工资读不同纳税区间的匹配
select a.user_name,a.money,b.low,b.high,b.rate from user1 a join taxRate b on a.money > b.low order by user_name;

image
2)利用least()函数确定每个区间的纳税额

select a.user_name,a.money,b.low,b.high,
least(money - low,high - low) as curmoney,b.rate from 
user1 a join taxRate b
on a.money > b.low order by a.user_name,curmoney;

image
3)每个区间缴税金额求和,就是每个人该缴的税

select user_name,sum(curmoney*rate) as tax from (
select user_name,money,low,high,least(money-low,high-low)as curmoney,rate
from user1 a join taxRate b on a.money > b.low) a
group by user_name;

image

posted @ 2018-04-02 22:28  Jenvid  阅读(168)  评论(0编辑  收藏  举报