一、准备数据
先要准备两张表,一张员工表(emp),一张部门表。
建表sql
sql
-- 准备数据
create table dept(
id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '部门名称'
)comment '部门表';
create table emp(
id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '姓名',
age int comment '年龄',
job varchar(20) comment '职位',
salary int comment '薪资',
entrydate date comment '入职时间',
managerid int comment '直属领导ID',
dept_id int comment '部门ID'
)comment '员工表';
-- 添加外键
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
INSERT INTO dept (id, name) VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部');
INSERT INTO emp (id, name, age, job,salary, entrydate, managerid, dept_id) VALUES
(1, '金庸', 66, '总裁',20000, '2000-01-01', null,5),
(2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1),
(3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1),
(4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1),
(5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1),
(6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1),
(7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3),
(8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3),
(9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3),
(10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2),
(11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2),
(12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2),
(13, '方东白', 19, '职员',5500, '2009-02-12', 10,2),
(14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4),
(15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4),
(16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4),
(17, '陈友谅', 42, null,2000, '2011-10-12', 1,null);
-- 多表查询 -- 笛卡尔积
select * from emp , dept where emp.dept_id = dept.id;
二. 多表关系概述
- 一对多
常见案例 :用户和部门。在通常情况下,在多的一方表里建立外键来进行关联。一个员工对应一个部门,一个部门可以有多个员工。
- 多对多
常见案例,学生和课程。一个学生可以选修多个课程,一个课程也可以有多个学生。通常多对多的情况下,会建立一张中间表,分别带有两方主键作为外键来建立关系。
- 一对一
常见案例,用户和用户详情。多用于单表拆分,将一张表的基础信息放在一张表中,其余详情信息放在另一张表中,以提高操作效率。通常在任意一方加入外键,关联另一方的主键。且外键设为唯一(UNIQUE)
三、多表查询
- 内连接
内连接查询的是两张表的交集。分为显示内连接和隐式内连接。
sql
-- 内连接演示
-- 1.查询每一个员工的姓名及对应的部门名称(隐式内连接)
-- 表结构: emp,dept
-- 连接关系: emp.dept_id = emp.id
select emp.name,dept.name
from emp,dept where dept.id = emp.dept_id;
-- 2.查询每一个员工的姓名及对应的部门名称(显式内连接)
-- 表结构: emp,dept
-- 连接关系: emp.dept_id = emp.id
select e.name,d.name from emp e inner join dept d on e.dept_id = d.id;
- 外连接
外连接包含左连接和右连接,左连接相当于查询左表的所有数据并包含左右表交集部分的数据。右连接则相当于查询右表的所有数据并包含左右表交集部分的数据。
sql
-- 外连接
-- 左外连接,查询左表的所有数据,并输出以之相匹配的右表数据
select e.name,d.name from emp e left join dept d on d.id = e.dept_id;
-- 右外连接,查询右表的所有数据,并输出以之相匹配的表表数据
select e.name,d.name from emp e right join dept d on d.id = e.dept_id;
- 自连接
顾名思义,内连接就是同一张表自己连接自己。可以是左连接或者右连接。需要用别名做区分。
这里直接看两个需求:
sql
-- 自连接 通过内连接,左连接,右连接都可以
-- 查询所有员工姓名且以之相对应的领导姓名(不包含没有领导的数据)
select e1.name as emp,e2.name as manager from emp e1 inner join emp e2 on e1.managerid = e2.id;
-- 查询所有员工姓名且以之相对应的领导姓名(包含没有领导的数据)
select e1.name as emp,e2.name as manager from emp e1 left join emp e2 on e1.managerid = e2.id;
第一张表e1当做是查询所有员工,第二张表e2可以当成查询领导。则关联关系为e1.id = e2.managerid。e1.name就是员工名,e2.name就是领导名称。至于包含不包含没有领导的数据,则就是外连接和内连接的区别了。
- 子查询
子查询分为三种情况:标量子查询、列子查询、行子查询和表子查询
标量子查询,即返回的结果只有单个值,常见的操作符 = ,<>, > ,<,<=,>=
sql
-- 标量子查询
-- 查询部门为销售部的员工
select * from emp left join dept on emp.dept_id = dept.id where dept.name = '销售部';
select * from emp where dept_id = (select id from dept where name = '销售部');
列子查询,即返回的结果为一列(可以是多行)
sql
-- 列子查询(可以是多行)
-- 查询销售部和市场部的员工信息
select emp.* from emp left join dept d on emp.dept_id = d.id where d.name = '销售部' or d.name = '市场部';
select * from emp where emp.dept_id in (select id from dept where dept.name = '销售部' or dept.name = '市场部');
-- 查询财务部比所有人工资都高的员工信息
select * from emp where salary > all (select salary from emp where dept_id = (select id from dept where dept.name = '财务部'));
-- 查询比研发部其中一人工资高的员工信息
select * from emp where salary > any (select salary from emp where dept_id = (select id from dept where dept.name = '研发部'));
行子查询,即返回的结果为一行,这种查询称为行子查询(多个属性可以用,隔开)
sql
-- 行子查询
-- 查询张无忌的薪资及其领导相同的员工信息
select * from emp where (salary,managerid) = (
select e1.salary,e1.managerid from emp e1 where e1.name = '张无忌'
);
select e1.* from emp e1
right join (select e1.salary,e1.managerid from emp e1 where e1.name = '张无忌') a on a.managerid = e1.managerid and a.salary = e1.salary;
表子查询,即返回的结果多行多列,这种查询称为行表查询(通常情况下可以进行关联)
sql
-- 表子查询
-- 查询'鹿杖客'与'宋远桥'的职位和薪资相同的员工信息
select * from emp right join
(
select job,salary
from emp where name = '鹿杖客' or name = '宋远桥'
) a on a.salary = emp.salary and emp.job = a.job
where name != '宋远桥' and name <> '鹿杖客';
select * from emp where (salary,job) in (select salary,job from emp where name = '鹿杖客' or name = '宋远桥' )
and name != '宋远桥' and name <> '鹿杖客';
-- 查询入职日期是'2006-01-01'之后的员工信息,及其部门信息
select emp.name,dept.name from emp left join dept on emp.dept_id = dept.id where emp.entrydate > '2006-01-01';
select a.name,dept.name
from dept right join
(
select dept_id,name from emp where entrydate > '2006-01-01'
) a on a.dept_id = dept.id;
四、练习
练习需要再建立一张工资等级表
sql
create table salgrade(
grade int,
losal int,
hisal int
) comment '薪资等级表';
insert into salgrade values (1,0,3000);
insert into salgrade values (2,3001,5000);
insert into salgrade values (3,5001,8000);
insert into salgrade values (4,8001,10000);
insert into salgrade values (5,10001,15000);
insert into salgrade values (6,15001,20000);
insert into salgrade values (7,20001,25000);
insert into salgrade values (8,25001,30000);
sql
-- 查询员工的姓名、年龄、职位、部门信息
select emp.name,emp.age,emp.job,dept.name from emp,dept where emp.dept_id = dept.id;
select emp.name,emp.age,emp.job,d.name from emp left join dept d on d.id = emp.dept_id;
-- 查询年龄小于30岁的员工姓名、年龄、职位、部门信息
select emp.name,emp.age,emp.job,d.name from emp left join dept d on d.id = emp.dept_id where emp.age < 30;
-- 查询拥有员工的部门ID、部门名称
select emp.name,emp.dept_id,d.name from emp left join dept d on d.id = emp.dept_id order by d.id;
-- 查询所有年龄大于40岁的员工,及其归属的部门名称;如果员工没有分配部门,也要显示出来
select emp.name,d.name from emp left join dept d on d.id = emp.dept_id where emp.age > 40;
-- 查询所有员工的工资等级
select e.name,e.salary,s.grade from salgrade s,emp e where e.salary >= s.losal and e.salary <= s.hisal;
-- 查询'研发部'所有员工的信息及工资等级
select
emp.name,
case
when salary > 20000 then 'A'
when 10000<emp.salary and salary <=20000 then 'B'
when 5000<emp.salary and salary <=10000 then 'C'
when 2000<emp.salary and salary <=5000 then 'D'
else '乞丐' end as level
from emp left join dept d on d.id = emp.dept_id where d.name = '研发部' order by salary;
-- 查询研发部员工的平均工资
select avg(salary) from emp left join dept d on d.id = emp.dept_id where d.name = '研发部';
-- 查询工资比灭绝高的员工信息
select emp.* from emp where salary > (select salary from emp where name = '灭绝');
-- 查询平均薪资高的员工信息
select * from emp where salary > (select avg(salary) from emp);
-- 查询低于本部门平均工资的员工信息
select emp.name,emp.salary,s.name from emp
left join
(
select d.id,d.name,avg(salary) as salary from emp left join dept d on d.id = emp.dept_id group by d.id,d.name
) s on s.id = emp.dept_id
where emp.salary < s.salary;
-- 查询所有部门信息,并统计部门的员工人数
select dept.id,dept.name,count(e.id) from dept left join emp e on dept.id = e.dept_id group by dept.name,dept.id order by dept.id;
-- 查询所有学生的选课情况,展示出学生名称,学号,课程名称