数据库SQL语句练习及答案

CREATE TABLE `t_dept` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `deptName` VARCHAR(30) DEFAULT NULL,
 `address` VARCHAR(40) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `t_emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(20) DEFAULT NULL,
  `age` INT(3) DEFAULT NULL,
 `deptId` INT(11) DEFAULT NULL,
empno int  not null,
 PRIMARY KEY (`id`),
 KEY `idx_dept_id` (`deptId`)
 #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
 INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
 INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);

1.所有有门派人员的信息(要求显示门派名称)

SELECT e.`name`,d.`deptName` FROM t_emp e INNER JOIN t_dept d ON e.`deptId`=d.`id`;

2. 列出所有人员及其门派信息

SELECT e.`name`,d.`deptName` FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`;

3. 列出所有门派

SELECT * FROM t_dept;

4. 所有无门派人士

SELECT * FROM t_emp WHERE deptId IS NULL;

5. 所有无人门派

SELECT d.* FROM  t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;

6. 所有人员和门派的对应关系

SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`

UNION

SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.`deptId`=d.`id`;

7. 所有没有入门派的人员和没人入的门派

SELECT * FROM t_emp e  LEFT JOIN t_dept d ON e.`deptId`=d.`id` WHERE e.deptId IS NULL

UNION

SELECT * FROM  t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;

8. 添加CEO字段

ALTER TABLE `t_dept`

add  CEO  INT(11)  ;

update t_dept set CEO=2 where id=1;

update t_dept set CEO=4 where id=2;

update t_dept set CEO=6 where id=3;

update t_dept set CEO=8 where id=4;

update t_dept set CEO=9 where id=5;

8.1 求各个门派对应的掌门人名称

SELECT d.deptName,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id

8.2求所有当上掌门人的平均年龄

SELECT AVG(e.age) FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id

8.3求所有人物对应的掌门名称

SELECT ed.name '人物',c.name '掌门' FROM

(SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed

 LEFT JOIN t_emp c on ed.ceo= c.id;

 

##查出每个人ceoid

SELECT e.name '人物',tmp.name '掌门'

FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp

ON e.deptId=tmp.did;

SELECT e1.name '人物',e2.name '掌门'

 FROM t_emp e1

 LEFT JOIN t_dept d on e1.deptid = d.id

 LEFT JOIN t_emp e2 on d.ceo = e2.id ;

 

 

##思路分析:一次性关联三张表 (所有人物对应部门,部门对应掌门人)

 

SELECT e2.name '人物',

(SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门'

 from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;

 

### 8.3俩个思路

# 两种思路: 1. 每个人,都有门派id 。 先求出每个门派,掌门名(tmp)!再拿t_emp关联tmp!
# 1.3s     3 ALL  1 eq_ref
EXPLAIN SELECT SQL_NO_CACHE e1.name 'empname',tmp.ceoname
FROM emp e1 LEFT JOIN 
(SELECT d.`id`,d.`deptName`,e.`name` 'ceoname'
FROM dept d INNER JOIN emp e
ON d.`CEO`=e.`id`) tmp
ON e1.deptId=tmp.id

# 2. 求每个人的ceoid,再关联员工表,求出ceoname    # 0.9s
#  2 ALL  2 eq_ref
EXPLAIN SELECT SQL_NO_CACHE tmp.empname,e1.name 'ceoname'
FROM 
(SELECT e.`name` 'empname',d.`CEO`
FROM emp e LEFT JOIN dept d
ON e.`deptId`=d.`id`) tmp LEFT JOIN emp e1
ON tmp.ceo=e1.id


# 3. 2的简写,一次性关联3个表   # 0.005s
#  1 个 all ,2 eq_ref
EXPLAIN SELECT SQL_NO_CACHE e.`name` 'empname',e1.name 'ceoname'
FROM  emp e 
LEFT JOIN dept d ON e.`deptId`=d.`id`
LEFT JOIN emp e1 ON d.ceo=e1.id

# 4. 2的思路利用分步查询实现: 求每个人的ceoid,直接利用分布查询,将ceoid替换为ceoname  0.006s
#  1 个 all ,2 eq_ref
EXPLAIN SELECT SQL_NO_CACHE e.`name` 'empname',(SELECT NAME FROM emp WHERE id=d.`CEO`) 'ceoname'
FROM emp e LEFT JOIN dept d
ON e.`deptId`=d.`id`


# 效果一样,效率不同!  4,3 > 2 > 1

#  在多表关联时,尽量一次性关联! 效率最好!
#  在多表关联时,尽量将虚表(衍生表)作为驱动表,将实体表作为被驱动表,建立索引进行优化!

 

 

加强版练习

1. 案例一

列出自己的掌门比自己年龄小的人员

select e1.name empname,e1.age empage,e2.name ceoname,e2.age ceoage 
from t_emp e1 inner join t_dept d on e1.deptid=d.id
inner join t_emp e2 on d.ceo=e2.id
where e1.age>e2.age;

数据库SQL语句练习及答案_第1张图片

优化: 

数据库SQL语句练习及答案_第2张图片

两次inner join的被驱动表都已经用上了索引。

2. 案例二

列出所有年龄低于自己门派平均年龄的人员

思路: 先取门派的平均年龄,再跟自己的年龄做对比!

select e1.name from t_emp e1 
inner join
(select deptid,AVG(age) avgage from t_emp 
group by deptid) tmp
on e1.deptid=tmp.deptid
where e1.age

数据库SQL语句练习及答案_第3张图片

 

在没有索引的前提下:

数据库SQL语句练习及答案_第4张图片

如何优化:

①首先在子查询中,需要根据deptid做groupby操作,因此,需要在deptid上面建立索引;

②因为是inner join,因此会自动将小表作为驱动表,也就是说,分组后的tmp是驱动表,而e1是被驱动表;

③而在e1中,需要查询deptid和age两个字段,因此这两个字段也需要建立索引

 

结果:创建deptid和age的符合索引:   create index idx_deptid_age on emp(deptid,age);

3. 案例三

列出至少有2个年龄大于40岁的成员的门派名称

思路: 先查询大于40岁的成员,然后按照门派分组,然后再判断至少有2个的门派!

select d.deptName,count(*) 
from t_emp e inner join t_dept d
on e.deptid=d.id
where e.age>40 
group by d.id,d.deptName 
having count(*)>=2

 

数据库SQL语句练习及答案_第5张图片

优化: 

数据库SQL语句练习及答案_第6张图片

优化:

①两表关联,我们可以考虑将小表作为驱动表。

②group by的字段 id,deptName还可以建立索引: create index idx_id_deptName on dept(id,deptName);

③被驱动表的deptid作为关联字段,可以建立索引:create index idx_deptid on emp(deptid);

create index idx_id_deptname on dept(id,deptName);

 

4. 案例四

至少有2位非掌门人成员的门派

select d2.deptName from t_emp e inner join t_dept d2 on e.deptid=d2.id

 left join t_dept d on e.id=d.ceo

 where d.id is null and e.deptid is not null

 group by d2.deptName,d2.id

 having count(*)>=2;

数据库SQL语句练习及答案_第7张图片

没有索引的情况下:

数据库SQL语句练习及答案_第8张图片

 

优化分析: 三个表关联,然后做group by分组!

①group by 的字段,可以加上索引:create index idx_deptname_id on dept(deptName,id);

②可以将部门表作为驱动表

③第一次join时,e表作为被驱动表,可以将deptid设置索引:create index idx_deptid on emp(deptid);

④最有一次join中,使用了dept表作为被驱动表,查询ceo字段,因此可以在ceo上面建立索引

create index idx_ceo on dept(ceo);

数据库SQL语句练习及答案_第9张图片

5. 案例五

列出全部人员,并增加一列备注“是否为掌门”,如果是掌门人显示是,不是掌门人显示否

select e.name,case when d.id is null then '否' else '是' end '是否为掌门' from t_emp e 
 left join t_dept d 
 on e.id=d.ceo;

数据库SQL语句练习及答案_第10张图片

优化:在d表的ceo字段建立索引即可!

6. 案例六

列出全部门派,并增加一列备注“老鸟or菜鸟”,若门派的平均值年龄>40显示“老鸟”,否则显示“菜鸟”

思路: 先从emp表求出,各门派的平均年龄,分组,然后在关联dept表,使用if函数进行判断!

select d.deptName,if(avg(age)>40,'老鸟','菜鸟') from t_emp e inner join t_dept d 
on d.id=e.deptid
group by d.deptName,d.id

数据库SQL语句练习及答案_第11张图片

优化

数据库SQL语句练习及答案_第12张图片

优化:

①使用dept作为驱动表

②在dept上建立deptName和id的索引:create index idx_deptName_id on dept(deptName,id);

③在emp上建立deptid字段的索引: create index index_deptid on emp(deptid);

7. 案例七

显示每个门派年龄最大的人

思路:先查询emp表,求出每个门派年龄最大的人,并按照deptid分组;然后再次关联emp表,关联其他的信息!

select * from t_emp e 
inner join
(select deptid,max(age) maxage 
from t_emp 
group by deptid) tmp
on tmp.deptid=e.deptid and tmp.maxage=e.age;

 

数据库SQL语句练习及答案_第13张图片

 

优化前:

 

优化思路:

①子查询中,emp表根据deptid进行分组,因此可以建立deptid字段的索引;

②inner join查询中,关联了age和deptid,因此可以在deptid,age字段建立索引

create index idx_deptid_age on emp(deptid,age);

 

你可能感兴趣的:(mysql)