数据库多表关系
数据库多表关系
今天整理的什么勾八东西 一团浆糊 学废了
为什么需要多表数据存储
- 如果所有关联数据都存在一个表中,数据会产生大量冗余
- 拆表存储以后,使用外键 值替代引用数据,防止数据冗余
- 拆表以后,产生多表查询,关联的数据表一定存在主外键关系!
表关系理解
- 关系型数据库数据是按照类别进行存储,类别 - >表
- 表之间存在关系【主外键】
- 表关系具体:表关系并不是表容器的关系,表关系的具体体现是数据的关系!
- 表关系种类
- 一对一
- 一对多
- 多对一
- 多对多
- 表关系:要双方对比,最后总结结果
表关系种类【主外键关系】
- 一对一
- 一对多
- 多对多
表关系实现(表设计者的角度)
一对一实现
CREATE DATABASE day04;
USE day04;
# 学生基本信息表
CREATE TABLE base_info(
bid INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生基本信息主键',
NAME VARCHAR(20) NOT NULL COMMENT '学生名'
)
# 方案1: 外键添加唯一约束即可 1:1
CREATE TABLE detail_info(
did INT PRIMARY KEY AUTO_INCREMENT COMMENT '详情表自己的主键',
address VARCHAR(200) NOT NULL COMMENT '详情地址',
bid INT UNIQUE COMMENT '学生的基本信息外键',
CONSTRAINT b_d_fk FOREIGN KEY (bid) REFERENCES base_info(bid)
)
#方案2: 外键+主键
CREATE TABLE detail_info1(
address VARCHAR(200) NOT NULL COMMENT '详情地址',
bid INT PRIMARY KEY COMMENT '学生的基本信息外键,也是主键',
CONSTRAINT b_de_fk FOREIGN KEY (bid) REFERENCES base_info(bid)
)
一对多实现
CREATE TABLE student(
sid INT PRIMARY KEY AUTO_INCREMENT COMMENT "学生主键",
sname VARCHAR (20) NOT NULL
)
CREATE TABLE score(
cid INT PRIMARY KEY AUTO_INCREMENT COMMENT "分数主键",
score INT DEFAULT 0 COMMENT '分数',
sid INT COMMENT '学生表外键',
CONSTRAINT s_s_fk FOREIGN KEY (sid) REFERENCES student (sid)
)
多对多实现
SQL DQL语句(多表查询)
多表查询介绍和理解
- 多表查询:就是将多个表的结果集合并到一起返回就是多表查询!
- 多表查询方式:垂直结果拼接到一起,水平结果拼接查询
- 垂直拼接:将两个结果集垂直方向拼接到一起,行和行数据之间没有任何的关联
- 垂直拼接:两个结果集的查询的列数要一致,对应的类型一致即可!!不要求两个表有主外键关系
- 水平拼接:将两个结果集水平拼接到一起,行和行数据之间要联动连接
- 水平拼接:将两个结果集的不同行,拼接成一个新的行,要求两个表必须存在关系 【有主外键】
垂直多表查询(合并结果集)
语法
#结果集1 UNION 结果集2; UNION会去掉重复数据
#结果集1 UNION ALL 结果集2; UNION ALL 不会去掉重复数据
注意
- 要合并的结果集列数必须一样
- 对应的列类型必须一致
表1
表1列名1 | 表1列名2 |
---|---|
INT类型 | VARCHAR类型 |
表2
表2列名1 | 表2列名2 |
---|---|
INT类型 | VARCHAR类型 |
代码实现
# 垂直合并结果集
# 数据准备
-- 创建表a 表b
CREATE TABLE a(
aid INT,
aname VARCHAR(10)
);
CREATE TABLE b(
bid INT,
bname VARCHAR(10)
);
-- 表中插入数据
INSERT INTO a VALUES(1,'aaaa'),(2,'bbbb'),(3,'cccc');
INSERT INTO b VALUES(4,'aaaa'),(2,'bbbb'),(3,'cccc');
#语法1
SELECT * FROM a
UNION # 会去掉重复数据! 所有列都重复,就是重复数据!
SELECT * FROM b
UNION
SELECT * FROM b;
#语法2
SELECT * FROM a
UNION ALL # 不会去掉重复数据! 所有列都重复,就是重复数据!
SELECT * FROM b
UNION ALL
SELECT * FROM b;
水平多表查询(水平连接查询)【重点】
水平查询明确 【连接查询为重点,99大法为辅助理解,自然连接是鸡肋,自连接是特殊方式】
- 将多个结果集水平拼接,行和行要融合
- 两个表之间必须存在关系 【主外键】
99查询大法【方言】
实现思路
- 就是模仿99乘法表
- 将两个表的数据全部水平拼接一遍,一定包含所有正常的组合
- 也会包含错误的组合
- 还需要自己使用WHERE进行条件筛选【有主外键】
- 其实就是内连接的另外写法
实现语法
- select * from 表1,表2,表3...表n where 主键 = 外键 and 主键 = 外键...
- 如果有n张表,就有n-1对主外键条件!
实现代码
准备数据
# 准备数据
-- 学生表
CREATE TABLE student(
stuid VARCHAR(10)PRIMARY KEY,
stuname VARCHAR(50)
);
-- 添加数据
SELECT * FROM student;
INSERT INTO student VALUES('1001','张三峰');
INSERT INTO student VALUES('1002','张无忌');
INSERT INTO student VALUES('1003','王尼玛');
INSERT INTO student VALUES('1004','王老五');
-- 分数表 多表查询的数据
CREATE TABLE score(
stuid VARCHAR(10),
score INT,
courseid INT,
CONSTRAINT fk_stu_sco FOREIGN KEY(stuid) REFERENCES student(stuid)
);
-- 添加数据
SELECT * FROM score;
INSERT INTO score VALUES('1001',98,1);
INSERT INTO score VALUES('1002',95,1);
INSERT INTO score VALUES('1002',67,2);
INSERT INTO score VALUES('1003',83,2);
INSERT INTO score VALUES('1003',57,3);
# 课程表
CREATE TABLE course(
cid INT PRIMARY KEY,
cname VARCHAR(20) COMMENT '课程名称',
tid INT COMMENT '授课老师编号'
);
-- 添加数据
INSERT INTO course(cid,cname,tid)
VALUES (1,'java',1),(2,'php',2),(3,'h5',3);
# 教师表
CREATE TABLE teacher(
tid INT PRIMARY KEY COMMENT '讲师编号',
tname VARCHAR(20) COMMENT '讲师名称'
);
-- 添加数据
INSERT INTO teacher(tid,tname)
VALUES (1,'迪迦'),(2,'戴拿'),(3,'盖亚');
查询
# 查询学生姓名和分数以及对应的学科
# 全部数据查询
SELECT * FROM student AS s,score c ,course u;
# 数据赛选
SELECT s.stuname,c.score,u.cname
FROM student AS s,score c ,course u
WHERE s.stuid = c.stuid
AND c.courseid = u.cid ;
# 查询学生姓名和分数以及课程已经对应的讲师姓名
# 四张表! 水平拼接
# 1.全部查询
SELECT * FROM student s,score c,course u,teacher t;
# 2.数据赛选
SELECT s.stuname,c.score,u.cname,t.tname
FROM student s,score c,course u,teacher t
WHERE s.stuid = c.stuid
AND c.courseid = u.cid
AND u.tid = t.tid
连接查询【普通话】
内连接
语法
#a. select 列 from 表1 [ inner] join 表2 on 主= 外 [inner] join 表3 on 主 = 外 where group by ...
#b. 规律:连接查询,就是两张表连接一次,on一次主外键,再连接第三张表,以此类推即可!!!
代码
# 2.1 内连接
# select * from 表1 别名 [inner] join 表2 别名 on 主 = 外 where group by having order by limit
# 使用内连接查询下你学生和学生分数
# 将两个表连接到一起,也有笛卡尔积 20条数据!
# from s,c
SELECT * FROM student s INNER JOIN score c
# on 只写主外键!
SELECT * FROM student s INNER JOIN score c ON s.stuid = c.stuid
# 内连接!!!! inner可以省略
SELECT * FROM student s JOIN score c ON s.stuid = c.stuid
# 查询学生姓名和分数以及课程名称!
# 学生表 分数表 课程表 【水平查询,99 还是 内外连接都要求有主外键!】
# 两两连接,
SELECT * FROM student s INNER JOIN score c ON s.stuid = c.stuid
INNER JOIN course u ON c.courseid = u.cid;
#学生姓名和分数以及课程以及授课老师!
SELECT s.stuname,c.score,u.cname,t.tname FROM student s INNER JOIN score c ON s.stuid = c.stuid
INNER JOIN course u ON c.courseid = u.cid
INNER JOIN teacher t ON u.tid = t.tid
WHERE c.score > 60;
# 没有三张以上的表查询! 我们都是两两处理!主外键!
# 最后如果有where 再添加即可!
外连接
语法
#语法:
SELECT * FROM 表1 [inner] JOIN 表2 ON 主 = 外
#外连接:
select * from 表1 left/right [outer] join 表2 on 主 = 外
#left和right的作用就是用来指定逻辑主表!
代码
#99大法和内连接查询效果是一样!
#他们都是一种公平查询法则,要求两个表必须存在主外键的值,结果才能显示!例如:没有王老五, 因为学生表王老五,但是分数表没有外键的值! 结果没有王老五!
#外连接: 就是了结果显示王老五!外连接不是公平查询法则,他可以通过左和右方向来指定逻辑主表!
#一旦被指定逻辑主表
#逻辑主表的数据再结果中全部会显示! 如果想要王老五,必须使用外连接!!
# 查询学生和分数
SELECT * FROM score c RIGHT OUTER JOIN student s ON c.stuid = s.stuid;
#方向就是指定你想要全部数据的表!
-- 两张表
SELECT * FROM student s LEFT JOIN score c ON c.stuid = s.stuid;
-- 如果是三张表呢?
SELECT * FROM student s
LEFT JOIN score c ON c.stuid = s.stuid
LEFT JOIN course u ON c.courseid = u.cid
LEFT JOIN teacher t ON u.tid = t.tid;
# left / right 外连接 join 内连接
内外连接的选择
- 只要有一个表的数据必须要全部展示,外连接!
- 规律
- 一旦你选择了外连接,那么后面的所有连接都是外连接!并且连接方向都一致!!
内外连接的区别
-
外连接有王老五,内连接没有王老五!
-
内连接和99大法他们是一类语法,都是公平法则,最后结果的数据要求双方都存在引用!查询没有王老五!
-
外连接是非公平查询法则,可以通过方向指定左或者右为逻辑主表,主表的数据一定再结果中存在! 查询有王老五!
自然连接
- 自连接也是多表查询,不是新的语法,只是新的方式!
- 如果需要的数据来自于一行,那么就是单表查询!
- 如果需要的数据来自于多行,那么就是多表查询!
- 表查询分为两种,一种正常不同多表【学生和分数】 另一种 自连接 【查员工和经理】
- 自连接:因为在设计表的时候,引用数据和被引用数据都存在一个表!还是不同行!
代码练习
# 4. 自连接 - 自己连接自己
/*
他不是一种新的语法,他是一种新的查询方式!
一个表使用多次!
学生表 连接 学生表 -》 自连接
*/
编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名
CREATE TABLE IF NOT EXISTS Employee (Id INT, NAME VARCHAR(255),
Salary INT, ManagerId INT)
INSERT INTO Employee (Id, NAME, Salary, ManagerId) VALUES ('1', 'Joe', '70000', '3');
INSERT INTO Employee (Id, NAME, Salary, ManagerId) VALUES ('2', 'Henry', '80000', '4');
INSERT INTO Employee (Id, NAME, Salary, ManagerId) VALUES ('3', 'Sam', '60000', 'None');
INSERT INTO Employee (Id, NAME, Salary, ManagerId) VALUES ('4', 'Max', '90000', 'None');
Employee 表包含所有员工,他们的经理也属于员工。
每个员工都有一个 Id,此外还有一列对应员工的经理的 Id。
+----+-------+--------+-----------+
| Id | NAME | Salary | ManagerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | MAX | 90000 | NULL |
+----+-------+--------+-----------+
给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过
他们经理的员工的姓名。
在上面的表格中,Joe 是唯一一个收入超过他的经理的员工。
+----------+
| Employee |
+----------+
| Joe |
+----------+
# 1. 查询员工姓名和经理名
joe sam
henry MAX
# 2. 行内数据对比
SELECT e1.*,e2.*
FROM Employee e1
INNER JOIN Employee e2 ON e1.ManagerId = e2.Id
WHERE e1.Salary > e2.Salary;
子查询/嵌套查询
概念
- 子查询也叫嵌套查询
- 子查询就是查询中嵌套其他的查询
- 子查询产生的原因:没有得到直接条件,得到了间接条件,需要根据间接条件返回数据再次查询!
语法
#查询中嵌套查询
#比如 和小明身高一样的同学
SELECT * FROM student
WHERE height = (select height from student where name = '小明');
子查询位置
单行单列
一般放在WHERE
后面,充当一个条件值,可以使用任何比较符号 > < = ! =
代码
#1.工资高于JONES的员工 【单行单列】
# 1.查询JONES的工资 单行单列
SELECT sal FROM emp WHERE ename ='JONES'
# 2.查询比他工资高的员工信息 > < =
SELECT * FROM emp WHERE sal > (SELECT sal FROM emp WHERE ename ='JONES')
#2.SCOTT同一个部门的员工 【单行单列】
#1.查询SCOTT的部门
SELECT deptno FROM emp WHERE ename = 'SCOTT'
#2.查询这个部门的员工
SELECT * FROM emp WHERE deptno = (SELECT deptno FROM emp WHERE ename = 'SCOTT')
多行单列
一般放在where
后面,充当多个条件值
多列等于比较: 例如:查询性别和身高与小明一样的同学! 可以使用 in关键字 (列名,列名) in (子查询 列名 ,列名) , 注意一一对应
多列等于比较:例如:查询性别一样但是身高高于小明的同学! 不用in了,拆分成单行单列再比较即可!
代码
#4.查询工作和工资与MARTIN完全相同的员工信息
# 1。查询martin的工作和工资
SELECT job,sal FROM emp WHERE ename = "MARTIN"
# 2. 多列等于
SELECT * FROM emp
WHERE (job,sal)
IN (SELECT job,sal FROM emp WHERE ename = "MARTIN")
SELECT * FROM emp
WHERE job = (SELECT job FROM emp WHERE ename = 'MARTIN')
AND sal = (SELECT sal FROM emp WHERE ename = 'MARTIN')
多行多列
都放在from 充当虚拟表!子查询结果集做表,****必须要起别名 from (子查询) 别名
代码
#5.有2个以上直接下属的员工信息
# 1.分组查询 有几个下属
SELECT 分组字段,聚合函数
# 查询出有两个以上下属的经理id和下属数量
SELECT mgr,COUNT(1) ct FROM emp GROUP BY mgr HAVING ct >2;
#2.虚拟结果充当表在和员工表连接一次
SELECT e1.empno,e1.ename,temp.ct FROM emp e1 JOIN
(SELECT mgr,COUNT(1) ct FROM emp GROUP BY mgr HAVING ct >2) temp
ON e1.empno = temp.mgr;
子查询总结
- 子查询就是间接条件查询
- 子查询可以出现在单表,也可以出现在多表