Mysql多表间的关系、连接查询以及子查询详解与使用案例

本专栏将从基础开始,循序渐进,讲解数据库的基本概念以及使用,希望大家都能够从中有所收获,也请大家多多支持。
专栏地址: 数据库必知必会
相关软件地址:软件地址
如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。

1 多表间的关系

1.1 为什么要有多表?

1.1.1 表的缺点

创建一个员工表包含如下列(id, name, age, dep_name, dep_location),id主键并自动增长,添加5条数据

CREATE TABLE emp (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(30),
	age INT,
	dep_name VARCHAR(30),
	dep_location VARCHAR(30)
);

-- 添加数据
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('张三', 20, '研发部', '广州');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('李四', 21, '研发部', '广州');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('王五', 20, '研发部', '广州');

INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('老王', 20, '销售部', '深圳');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('大王', 22, '销售部', '深圳');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('小王', 18, '销售部', '深圳');

缺点:表中出现了很多重复的数据(数据冗余),如果要修改研发部的地址需要修改3个地方。

解决方案:将一张表分成2张表(员工表和部门表)

-- 创建部门表
CREATE TABLE department (
	id INT PRIMARY KEY AUTO_INCREMENT,
	dep_name VARCHAR(20),
	dep_location VARCHAR(20)
);

-- 创建员工表
CREATE TABLE employee (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	age INT,
	dep_id INT
);

-- 添加2个部门
INSERT INTO department (dep_name, dep_location) VALUES ('研发部', '广州');
INSERT INTO department (dep_name, dep_location) VALUES('销售部', '深圳');

-- 添加员工,dep_id表示员工所在的部门
INSERT INTO employee (NAME, age, dep_id) VALUES 
('张三', 20, 1), 
('李四', 21, 1), 
('王五', 20, 1), 
('老王', 20, 2),
('大王', 22, 2),
('小王', 18, 2);

问题
当我们在employee的dep_id里面输入不存在的部门,数据依然可以添加。但是并没有对应的部门,不能出现这种情况。employee的dep_id中的内容只能是department表中存在的id

因此需要约束dep_id字段的值, 只能是department表中已经存在id。
解决方式:使用外键约束

1.2 外键约束

1.2.1 外键约束作用

  • 用来维护多表之间关系

外键: 一张从表中的某个字段引用主表中的主键
主表: 约束别人
副表/从表: 使用别人的数据,被别人约束

1.2.2 外键的语法

1.2.2.1 添加外键

1. 新建表时增加外键:
[CONSTRAINT] [外键约束名称] FOREIGN KEY(外键字段名) REFERENCES 主表名(主键字段名)
关键字解释:
CONSTRAINT -- 约束关键字
FOREIGN KEY(外键字段名)- 某个字段作为外键
REFERENCES -- 主表名(主键字段名) 表示参照主表中的某个字段

中括号[]对应的关键项可以省略。

2. 已有表增加外键:
ALTER TABLE 从表 ADD [CONSTRAINT] [外键约束名称] FOREIGN KEY (外键字段名) REFERENCES 主表(主键字段名);

-- 删除员工表,从新创建,并添加外键
方式1CREATE TABLE employee (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	age INT,
	dep_id INT,
	-- 添加一个外键
	-- 外键取名公司要求,一般fk结尾
	CONSTRAINT emp_depid_ref_dep_id_fk FOREIGN KEY(dep_id) REFERENCES department(id)
);
方式2(省略中括号中的关键词):
CREATE TABLE employee (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	age INT,
	dep_id INT,
	-- 添加一个外键
	FOREIGN KEY(dep_id) REFERENCES department(id)
);

-- 添加正确数据
INSERT INTO employee (NAME, age, dep_id) VALUES
('张三', 20, 1),
('李四', 21, 1),
('王五', 20, 1),
('老王', 20, 2),
('大王', 22, 2),
('小王', 18, 2);


-- 验证: 添加错误数据
INSERT INTO employee (NAME, age, dep_id) VALUES ('二王', 20, 5);// 报错

1.2.2.2 删除外键

  • alter table 表 drop foreign key 外键名称;

    -- 删除employee员工表的外键
    alter table employee drop foreign key emp_dep_fk1;
    -- 往员工信息表中添加非法数据---部门id不存在
    INSERT INTO employee (NAME, age, dep_id) VALUES ('老张', 18, 6);-- 成功
    
  • 为已存在的表添加外键,注意:外键字段上不能有非法数据

  • alter table 表名 add [constraint] [外键名称] foreign key(外键字段名) reference 主表(主键名)

  • -- 往员工信息表中添加非法数据---部门id不存在
    INSERT INTO employee (NAME, age, dep_id) VALUES ('老张', 18, 6);-- 失败
    

1.2.2.3 外键的级联

  • 要把部门表中的id值2,改成5,能不能直接修改呢?

    UPDATE department SET id=5 WHERE id=2;
    

    不能直接修改:

    Cannot delete or update a parent row: a foreign key constraint fails

    如果副表(员工表)中有引用的数据,不能直接修改主表(部门表)主键

    要删除部门id等于1的部门, 能不能直接删除呢?

    DELETE FROM department WHERE id = 1;
    

    不能直接删除:

    Cannot delete or update a parent row: a foreign key constraint fails

    如果副表(员工表)中有引用的数据,不能直接删除主表(部门表)数据

    什么是级联操作
    在修改和删除主表的主键时,同时更新或删除副表的外键值,称为级联操作
    ON UPDATE CASCADE – 级联更新,主表数据发生变化,从表数据也会发生变化
    ON DELETE CASCADE – 级联删除,主表数据被删除,从表数据也会被删除

    具体操作:

    • 删除employee表
    • 重新创建employee表,添加级联更新和级联删除
    CREATE TABLE employee (
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	NAME VARCHAR(30),
    	age INT,
    	dep_id INT,
    	CONSTRAINT employee_dep_fk FOREIGN KEY (dep_id) REFERENCES department(id) ON UPDATE CASCADE ON DELETE CASCADE
    );
    
  • 再次添加数据到员工表和部门表
  INSERT INTO employee (NAME, age, dep_id) VALUES ('张三', 20, 1);
  INSERT INTO employee (NAME, age, dep_id) VALUES ('李四', 21, 1);
  INSERT INTO employee (NAME, age, dep_id) VALUES ('王五', 20, 1);
  INSERT INTO employee (NAME, age, dep_id) VALUES ('老王', 20, 2);
  INSERT INTO employee (NAME, age, dep_id) VALUES ('大王', 22, 2);
  INSERT INTO employee (NAME, age, dep_id) VALUES ('小王', 18, 2);
  • 把部门表中id等于1的部门改成id等于10
  UPDATE department SET id=10 WHERE id=1;

  • 删除部门号是2的部门
  DELETE FROM department WHERE id=2;

小结

  1. 外键约束的作用: 维护多表的关系, 保证引用数据的完整性
  2. 外键的语法
constraint 外键名称 foreign key(外键字段名) references 主表(列[主键]) [ON UPDATE CASCADE][ON DELETE  CASCADE]
  1. 外键注意事项
    • 外键的这个列的类型必须和参照主表主键列的类型一致
    • 参照列必须是主键

1.3 多表间关系

​ 现实生活中,实体与实体之间肯定是有关系的,比如:老公和老婆,部门和员工,老师和学生等。那么我们在设计表的时候,就应该体现出表与表之间的这种关系!分成三种:

  1. 一对多
  2. 多对多
  3. 一对一

1.3.1 一对多(1:n)

例如:班级和学生,部门和员工,客户和订单

一的一方: 班级 部门 客户

多的一方:学生 员工 订单

一对多建表原则: 在从表(多方的一方)创建1一个字段,字段作为外键指向主表(一方)的主键

1.3.2 多对多

多对多(m:n)
例如:老师和学生,学生和课程,用户和角色

一个老师可以有多个学生,一个学生也可以有多个老师 多对多的关系

一个学生可以选多门课程,一门课程也可以由多个学生选择 多对多的关系

一个用户可以有多个角色,一个角色也可以有多个用户 多对多的关系

多对多关系建表原则: 需要创建第三张表,中间表中至少两个字段,这两个字段分别作为外键指向各自一方的主键。

1.3.3 一对一(通常单表)

一对一(1:1)

例如: 一个公司可以有一个注册地址,一个注册地址只能对一个公司。

例如:一个老公可以有一个老婆,一个老婆只能有一个老公

在实际的开发中应用不多.因为一对一可以创建成一张表
两种建表原则:

  • 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一UNIQUE

  • 外键是主键:主表的主键和从表的主键,形成主外键关系

1.3.4 多表设计之多表分析及创建

需求:完成一个学校的选课系统,在选课系统中包含班级,学生和课程这些实体。

分析:

班级和学生: 一对多

学生和课程: 多对多

  1. 班级和学生之间是有关系存在:

    一个班级下包含多个学生,一个学生只能属于某一个班级(一对多的关系)。

  2. 学生和课程之间是有关系存在:

    一个学生可以选择多门课程,一门课程也可以被多个学生所选择(多对多的关系)。

    -- 多表建表原则练习
    -- 班级和学生: 一对多
    -- 学生和课程: 多对多
    -- 创建班级表
    create table class(
    	cid int primary key auto_increment,
    	cname varchar(40)
    );
    -- 创建学生表
    create table student(
    	sid int primary key auto_increment,
    	sname varchar(40),
    	c_id int,
    	constraint stu_cls_fk1 foreign key(c_id) references class(cid)
    );
    
    -- 创建课程表
    create table course(
    	co_id int primary key auto_increment,
    	co_name varchar(40)
    );
    
    -- 创建中间表
    create table stu_co(
    	sno int,
    	cno int,
    	constraint stu_co_fk1 foreign key(sno) references student(sid),
    	constraint stu_co_fk2 foreign key(cno) references course(co_id)
    );
    

小结

  1. 1对多: 在多方创建一个字段作为外键 指向一方的主键
  2. 多对多: 创建一张中间表,这个表里面至少包含两个字段,都作为外键,分别指向各自一方的主键
  3. 1对1: 先当做1对多, 再在外键列添加唯一约束;一般开发中是创建一张表

2 连接查询

2.1 环境准备

-- 创建部门表
CREATE TABLE dept (
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(20)
);

INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部');

-- 创建员工表
CREATE TABLE emp (
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(10),
  gender CHAR(1),   -- 性别
  salary DOUBLE,   -- 工资
  join_date DATE,  -- 入职日期
  dept_id INT
);

INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孙悟空','男',7200,'2013-02-24',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('猪八戒','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('蜘蛛精','女',4500,'2011-03-14',1);

2.2 交叉查询

​ 交叉查询把若干张表(>=2)没有条件的连接在一起,进行展示

  1. 语法
select ... from1,2 ;  

select a.字段,b.字段 from a,b ;  
select a.*,b.* from a,b ;  
--或者 
select * from a,b;
  1. 练习: 使用交叉查询部门和员工
SELECT * FROM dept, emp;

以上数据其实是左表的每条数据和右表的每条数据组合。左表有3条,右表有5条,最终组合后3*5=15条数据。

左表的每条数据和右表的每条数据组合,这种效果称为笛卡尔乘积

小结

  • 交叉查询其实是一种错误.数据大部分是无用数据,叫笛卡尔积.
  • 假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)}。可以扩展到多个集合的情况。

2.3 内连接查询

​ 交叉查询产生这样的结果并不是我们想要的,那么怎么去除错误的,不想要的记录呢,当然是通过条件过滤。通常要查询的多个表之间都存在关联关系,那么就通过==关联关系(主外键关系)==去除笛卡尔积。

2.3.1 隐式内连接

隐式里面是没有inner关键字的

select [字段,字段,字段][*] from1,2 where 连接条件 --(外键的值等于主键的值) 
select * from emp,dept where emp.dept_id = dept.id;
  • 练习:查询员工的id,姓名,性别,薪资,加入日期,所属部门

    select emp.id,emp.name,emp.gender,emp.salary,emp.join_date,dept.name from emp,dept where emp.dept_id = dept.id;
    -- 取别名---开发中一般使用取别名的方式
    select e.id,e.name,e.gender,e.salary,e.join_date,d.name from emp e,dept d where e.dept_id = d.id;
    

2.3.2 显示内连接

显示里面是有inner关键字的

select [字段,字段,字段][*] from a [inner] join b on 连接条件 [ where 其它条件]
select * from emp inner join dept on emp.dept_id = dept.id
select * from emp inner join dept on emp.dept_id = dept.id where emp.id = 2
select * from emp  join dept on emp.dept_id = dept.id where emp.id = 2

练习

查询所有部门下的员工信息,如果该部门下没有员工则不展示.

insert into dept values(null,'设计部');
insert into emp values(null,'吴承恩','男',10000,'2000-01-01',null);
-- 隐式内连接查询
select * from emp e,dept d where e.dept_id = d.id;
-- 显示内连接查询
select * from emp e inner join dept d on e.dept_id = d.id;

小结

  1. 内连接的特点(查的是什么东西)

    内连接查询的是公共部分,满足连接条件(主外键关系)的部分

  2. 使用内连接的关键点

    • 使用主外键关系做为条件来去除无用信息。 抓住主外键的关系,用主外键作为连接条件 b表里面的外键 = a表里面的主键
    • 显示内连接里面的,on只能用主外键关联作为条件,如果还有其它条件,后面加where
  3. 语法

-- 隐式(不出现inner)
select * from a,b where a.主键=b.外键 and 其它条件

-- 显示(出现inner)
select * from a [inner] join b on a.主键=b.外键 where 其它条件

2.4 外连接

​ 我们发现内连接查询出来的是公共部分. 如果要保证某张表的全部数据情况下进行连接查询. 那么就要使用外连接查询了. 外连接分为左外连接和右外连接

2.4.1 左外连接

​ 以join左边的表为主表,展示主表的所有数据,根据条件查询连接右边表的数据,若满足条件则展示,若不满足则以null显示.

​ 可以理解为:在内连接的基础上保证左边表的数据全部显示

  1. 语法
select [字段][*] from a left [outer] join b on 条件
  1. 练习:查询所有部门下的员工
SSELECT * FROM dept LEFT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;

2.4.2 右外连接

​ 以join右边的表为主表,展示右边表的所有数据,根据条件查询join左边表的数据,若满足则展示,若不满足则以null显示

可以理解为:在内连接的基础上保证右边表的数据全部显示

  1. 语法
select 字段 from a right [outer] join b on 条件
  1. 练习:查询所有员工所对应的部门
SELECT * FROM dept RIGHT OUTER JOIN emp ON emp.dept_id=dept.id;

小结

  1. 语法
select * from a left [outer] join b on 连接条件  --左外连接
select * from a right [outer] join b on 连接条件 --右外连接
  1. 内连接和外连接的区别
    • 内连接: 查询的是公共部分,满足连接条件的部分
    • 左外连接: 以左边表为主表, 查询出左边表的所有的数据. 再通过连接条件匹配出右边表的数据, 如果满足连接条件, 展示右边表的数据; 如果不满足, 右边的数据通过null代替
    • 右外连接: 以右边表为主表, 查询出右边表的所有的数据. 再通过连接条件匹配出左边表的数据, 如果满足连接条件, 展示左边表的数据; 如果不满足, 左边的数据通过null代替
  2. 应用
1.用户1和订单m
  查询所有的用户的订单信息		外连接
  查询下单的用户的信息          内连接

2.用户1和账户m
  查询所有的用户的账户信息      外连接
  查询所有用户的开户信息        内连接

3 子查询

3.1 什么是子查询

我们刚刚讲解了内连接和外连接查询, 但是如果遇到很复杂的场景, 内连接和外连接查询可能查询不出来.我们就可以使用子查询了。直观一点: 一个查询语句里面至少包含2个select

  • 一个查询语句的结果作为另一个查询语句的条件

  • 有查询的嵌套,内部的查询称为子查询

  • 子查询要使用括号

  • 子查询结果的三种情况:

    1. 子查询的结果是一个值的时候
    2. 子查询结果是单列多行的时候
    3. 子查询的结果是多行多列

小结

  1. 子查询嵌套查询, 查询的语句可以作为另外一个查询语句的条件. 直观一点: 一条语句里面包含了多个select

3.2 子查询进阶

​ 尽管子查询的语法很灵活,没有固定的写法.但是它也有一些规律.

3.2.1 子查询的结果是一个值的时候

子查询结果只要是单个值,肯定在WHERE后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段[= > < <>](子查询);

  1. 查询工资最高的员工是谁?

    1. 查询最高工资是多少
      SELECT MAX(salary) FROM emp;
    

    1. 根据最高工资到员工表查询到对应的员工信息
      SELECT * FROM emp WHERE salary=(SELECT MAX(salary) FROM emp);
    

  2. 查询工资小于平均工资的员工有哪些?

    1. 查询平均工资是多少
      SELECT AVG(salary) FROM emp;
    

    1. 到员工表查询小于平均的员工信息
      SELECT * FROM emp WHERE salary < (SELECT AVG(salary) FROM emp);
    

3.2.2 子查询结果是单列多行的时候

子查询结果只要是单列,肯定在WHERE后面作为条件
子查询结果是单列多行,结果集类似于一个数组,父查询使用IN运算符
SELECT 查询字段 FROM 表 WHERE 字段 IN (子查询);

  1. 查询工资大于5000的员工,来自于哪些部门的名字

    1. 先查询大于5000的员工所在的部门id
      SELECT dept_id FROM emp WHERE salary > 5000;
    

    1. 再查询在这些部门id中部门的名字
      SELECT dept.name FROM dept WHERE dept.id IN (SELECT dept_id FROM emp WHERE salary > 5000);
    
  2. 查询开发部与财务部所有的员工信息

    1. 先查询开发部与财务部的id
    SELECT id FROM dept WHERE NAME IN('开发部','财务部');
    

    1. 再查询在这些部门id中有哪些员工
    SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME IN('开发部','财务部'));
    

3.2.3 子查询的结果是多行多列

子查询结果只要是多行多列,肯定在FROM后面作为
SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件;
子查询作为表需要取别名,否则这张表没用名称无法访问表中的字段

  • 查询出2011年以后入职的员工信息,包括部门名称

    1. 在员工表中查询2011-1-1以后入职的员工
    SELECT * FROM emp WHERE join_date > '2011-1-1';
    

    1. 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门id等于的dept_id
    SELECT * FROM dept d, (SELECT * FROM emp WHERE join_date > '2011-1-1') e WHERE e.dept_id = d.id;
    

小结

  1. 子查询的结果是单行单列(一个值情况), 一般放在where后面作为条件**, 通过=,>,<,<>**
select ... from ... where 列 [=><<>...] (子查询) 
  1. 子查询的结果是单列多行, 一般放在where后面作为条件**, 通过in**
select ... from ... where 列 in (子查询) 
  1. 子查询的结果是多行多列, 一般放在from后面作为虚拟表, 需要给虚拟表取别名
select ... from (子查询) as 别名   where 条件

猜你喜欢

转载自blog.csdn.net/Learning_xzj/article/details/125004864