From 8b7af2ddbaee84ed7003616d6a716e2ba4e02345 Mon Sep 17 00:00:00 2001 From: 7Wate Date: Wed, 11 May 2022 18:17:49 +0800 Subject: [PATCH] =?UTF-8?q?SQL=EF=BC=9A=E5=9F=BA=E7=A1=80=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/开发/SQL/基础.md | 1202 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1202 insertions(+) create mode 100644 docs/开发/SQL/基础.md diff --git a/docs/开发/SQL/基础.md b/docs/开发/SQL/基础.md new file mode 100644 index 00000000..65b3b576 --- /dev/null +++ b/docs/开发/SQL/基础.md @@ -0,0 +1,1202 @@ +--- +id: 基础 +title: 基础 +sidebar_position: 1 +data: 2022年5月11日 +--- + +## 数据库 + +| 名称 | 全称 | 简称 | +| :------------- | :----------------------------------------------------------- | :-------------------------------- | +| 数据库 | 存储数据的仓库,数据是有组织的进行存储 | DataBase(DB) | +| 数据库管理系统 | 操纵和管理数据库的大型软件 | DataBase Management System (DBMS) | +| SQL | 操作关系型数据库的编程语言,定义了一套操作 关系型数据库统一标准 | Structured Query Language (SQL) | + +## 数据模型 + +关系型数据库(RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库。 +而所谓二维表,指的是由行和列组成的表,可以通过一列关联另外一个表格中的某一列数据,如下图。 + +特点: + +1. 使用表存储数据,格式统一,便于维护。 +2. 使用 SQL 语言操作,标准统一,使用方便。 + + + +![img](https://static.7wate.com/img/2022/05/11/6bbfdea71bff2.png) + + + +常见的 MySQL、Oracle、DB2、SQLServer 这些都是属于关系型数据库,里面都是基于二维表存储数据的。 + +无论我们使用哪一个关系型数据库,最终在操作时都是使用 SQL 语言来进行统一操作, +因为我们接下来学习的 SQL 语言,是操作关系型数据库的统一标准。 + +![服务器](https://static.7wate.com/img/2022/05/11/b7d5d1651689a.png) + +## SQL + +全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准。 + + + +![img](https://static.7wate.com/img/2022/05/11/9459b0983c451.png) + +1. SQL 语句可以单行或多行书写,以分号结尾。 +2. SQL 语句可以使用空格 / 缩进来增强语句的可读性。 +3. MySQL 数据库的 SQL 语句不区分大小写,**关键字建议使用大写**。 +4. 注释: + 1. 单行注释:-- 注释内容 或 # 注释内容 + 2. 多行注释:/ *注释内容* / + +SQL 语句,根据其功能,主要分为四类:DDL、DML、DQL、DCL。 + +| 分类 | 说明 | +| :--- | :----------------------------------------------------- | +| DDL | 数据定义语言,用来定义数据库对象 (数据库,表,字段) | +| DML | 数据操作语言, 用来对数据库表中的数据进行增删改 | +| DQL | 数据查询语言,用来查询数据库中表的记录 | +| DCL | 数据控制语言,用来创建数据库用户、控制数据库的访问权限 | + +## DDL + +数据定义语言,定义数据库对象 (数据库,表,字段) + +### 数据库操作 + +查询所有数据库 + +```sql +SHOW DATABASES +``` + +使用某个数据库 + +```sql +USE 数据库名; +``` + +查询当前数据库 + +```sql +SELECT DATABASE(); +``` + +创建数据库 + +UTF8 字符集长度为 3 字节,有些符号占 4 字节,所以创建数据库时推荐用 utf8mb4 字符集 + +```sql +CREATE DATABASE [ IF NOT EXISTS ] 数据库名 [ DEFAULT CHARSET 字符集] [COLLATE 排序规则 ]; +``` + +删除数据库 + +```sql +DROP DATABASE [ IF EXISTS ] 数据库名; +``` + +### 数据表操作 + +创建表 + +最后一个字段后面是没有逗号的。 + +```sql +CREATE TABLE 表名( + 字段1 字段1类型 [COMMENT 字段1注释], + 字段2 字段2类型 [COMMENT 字段2注释], + 字段3 字段3类型 [COMMENT 字段3注释], + ... + 字段n 字段n类型 [COMMENT 字段n注释] +)[ COMMENT 表注释 ]; +``` + +查询表结构 + +```sql +DESC 表名; +``` + +查询当前数据库所有表,**必须处在数据库中** + +```sql +SHOW TABLES; +``` + +查询某表的建表语句 + +```sql +SHOW CREATE TABLE 表名; +``` + +表中添加字段 + +```sql +ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束]; +-- 例如 +ALTER TABLE emp ADD nickname varchar(20) COMMENT '用户昵称'; +``` + +修改数据类型 + +```sql +ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度); +-- 例如 +ALTER TABLE emp MODIFY nickname varchar(30); +``` + +修改字段名和字段类型 + +```sql +ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束]; +-- 例如 +ALTER TABLE emp CHANGE nickname name varchar(40); +``` + +删除字段 + +```sql +ALTER TABLE 表名 DROP 字段名; +-- 例如 +ALTER TABLE emp drop nickname; +``` + +修改表名 + +```sql +ALTER TABLE 表名 RENAME TO 新表名; +-- 例如 +ALTER TABLE emp RENAME TO empNew; +``` + +删除表 + +```sql +DROP TABLE [IF EXISTS] 表名; +-- 例如 +DROP TABLE [IF EXISTS] emp; +``` + +删除表,并重新创建该表 + +```sql +TRUNCATE TABLE 表名; +-- 例如 +TRUNCATE TABLE emp; +``` + +## DML + +数据操作语言,用来对数据库表中的数据进行增删改 + +字符串和日期类型数据应该包含在引号中 + +插入的数据大小应该在**字段的规定范围**内 + +指定字段添加数据 + +```sql +INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...); +``` + +全部字段添加数据 + +```sql +INSERT INTO 表名 VALUES (值1, 值2, ...); +``` + +批量添加数据 + +```sql +--指定字段 +INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...); +--全部字段 +INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...); +``` + +修改数据 + +```sql +UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [ WHERE 条件 ]; +-- 例如 +UPDATE emp SET nickname = '乐心湖' WHERE id = 1; +``` + +删除数据 + +```sql +DELETE FROM 表名 [ WHERE 条件 ]; +``` + +## DQL + +数据查询语言,用来查询数据库中表的记录 + +### 基础查询 + +```sql +SELECT + 字段列表 +FROM + 表名字段 +WHERE + 条件列表 +GROUP BY + 分组字段列表 +HAVING + 分组后的条件列表 +ORDER BY + 排序字段列表 +LIMIT + 分页参数 +``` + +查询所有字段 + +```sql +SELECT * FROM 表名; +-- 实际开发中尽量不要写 * 而是建议把每个字段都写出来 +SELECT id,name,age... FROM emp; +``` + +查询结果字段带别名 + +```sql +SELECT 字段1 [ AS 别名1 ], 字段2 [ AS 别名2 ], 字段3 [ AS 别名3 ], ... FROM 表名; +SELECT 字段1 [ 别名1 ], 字段2 [ 别名2 ], 字段3 [ 别名3 ], ... FROM 表名; +``` + +去除重复记录 + +```sql +SELECT DISTINCT 字段列表 FROM 表名; +``` + +### 条件查询 + +```sql +SELECT 字段列表 FROM 表名 WHERE 条件列表; +``` + +条件列表 + +| 比较运算符 | 功能 | +| :-------------- | :------------------------------------------ | +| > | 大于 | +| >= | 大于等于 | +| < | 小于 | +| <= | 小于等于 | +| = | 等于 | +| `<>` 或 != | 不等于 | +| BETWEEN … AND … | 在某个范围内(含最小、最大值) | +| IN(…) | 在 in 之后的列表中的值,多选一,或的意思 | +| LIKE 占位符 | 模糊匹配(_匹配单个字符,% 匹配任意个字符) | +| IS NULL | 是 NULL | + +| 逻辑运算符 | 功能 | | | +| :--------- | :----------------------- | ---- | ---------------------------- | +| AND 或 && | 并且(多个条件同时成立) | | | +| OR 或 \ | \ | | 或者(多个条件任意一个成立) | +| NOT 或 ! | 非,不是 | | | + +```sql +-- 年龄等于30 +select * from emp where age = 30; +-- 没有身份证 +select * from emp where idcard is null or idcard = ''; +-- 有身份证 +select * from emp where idcard; +select * from emp where idcard is not null; +-- 不等于 +select * from emp where age != 30; +-- 年龄在20到30之间 +select * from emp where age between 20 and 30; +select * from emp where age >= 20 and age <= 30; +-- 下面语句不报错,但查不到任何信息,因此一定要从先写小再写大 +select * from emp where age between 30 and 20; +-- 性别为女且年龄小于30 +select * from emp where age < 30 and gender = '女'; +-- 年龄等于25或30或35 +select * from emp where age = 25 or age = 30 or age = 35; +select * from emp where age in (25, 30, 35); +-- 姓名为两个字 +select * from emp where name like '__'; +-- 姓名第一个字为钟 +select * from emp where name like '钟%'; +-- 姓名最后第一个字为雪 +select * from emp where name like '%雪'; +-- 身份证最后为X +select * from emp where idcard like '%X'; +``` + +### 聚合查询 + +将一列数据作为一个整体,进行纵向计算。直接作用于字段。 + +```sql +SELECT 聚合函数(字段列表) FROM 表名; +``` + +常见聚合函数如下,注意:所有 null 值不参与聚合运算。 + +| 函数 | 功能 | +| :---- | :------- | +| count | 统计数量 | +| max | 最大值 | +| min | 最小值 | +| avg | 平均值 | +| sum | 求和 | + +```sql +-- 统计企业员工数量 +select count(id) from emp; +-- 统计企业平均年龄 +select avg(age) from emp; +-- 统计西安地区员工的年龄之和 +select sum(age) from emp where workaddr = '西安'; +``` + +### 分组查询 + +```sql +SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后的过滤条件 ]; +``` + +分组往往伴随着聚合 + +where 和 having 的区别: + +- 执行时机不同:where 是分组之前进行过滤,不满足 where 条件不参与分组;having 是分组后对结果进行过滤。 +- 判断条件不同:where 不能对聚合函数进行判断,而 having 可以。 + +注意: + +- 执行顺序:where > 聚合函数 > having +- 分组之后,**查询的字段一般为聚合函数和分组字段**,查询其他字段无任何意义 + +```sql +-- 根据性别分组,统计男性和女性数量(只显示分组数量,不显示哪个是男哪个是女) +select count(*) from emp group by gender; +-- 根据性别分组,统计男性和女性数量 +select gender, count(*) from emp group by gender; +-- 根据性别分组,统计男性和女性的平均年龄 +select gender, avg(age) from emp group by gender; +-- 年龄小于45,并根据工作地址分组,获取员工数量 +select workaddr, count(*) from emp where age < 45 group by workaddr; +-- 年龄小于45,并根据工作地址分组,获取员工数量大于等于3的工作地址 +select workaddr, count(*) address_count from emp where age < 45 group by workaddr having address_count >= 3; +``` + +### 排序查询 + +如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序 + +```sql +SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2; +``` + +排序方式 + +- ASC:升序(默认) +- DESC:降序 + +```sql +-- 根据年龄升序排序 +select * from emp order by age ASC; +select * from emp order by age; +-- 根据年龄对公司的员工进行升序排序,年龄相同,再按照入职时间进行降序排序 +select * from emp order by age ASC, entrydate DESC; +``` + +### 分页查询 + +```sql +SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数; +``` + +- 起始索引从 0 开始,所以这里有个公式,起始索引 = (查询页码 - 1) * 每页显示记录数 +- **分页查询是数据库的方言,不同数据库有不同实现,MySQL 是 LIMIT** +- 如果查询的是第一页数据,起始索引可以省略,直接简写 LIMIT 10 + +```sql +-- 查询第一页数据,展示10条 +select * from emp limit 0, 10; +-- 查询第二页,每页展示10条 +select * from emp limit 10, 10; +``` + +### DQL 练习 + +DQL 编写顺序: + +```sql +SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT +``` + +DQL 执行顺序: + +```sql +FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT +``` + + + +![img](https://static.7wate.com/img/2022/05/11/e526304cef38f.png) + + + +按照需求完成如下 DQL 语句编写 + +```sql +-- 1.查询年龄为20,21,22,23岁的员工信息。 +select * from emp where age in(20,21,22,23); +-- 2.查询性别为男,并且年龄在20-40岁(含)以内的姓名为三个字的员工。 +select * from emp where gender = '男' and age between 20 and 40 and name like '___'; +-- 3.统计员工表中,年龄小于60岁的,男性员工和女性员工的人数。 +select gender, count(*) from emp where age < 60 group by gender; +-- 4.查询所有年龄小于等于35岁员工的姓名和年龄,并对查询结果按年龄升序排序,如果年龄相同按入职时间降序排序。 +select name, age from emp where age < 35 order by age ASC, entrydata DESC; +-- 5、查询性别为男,且年龄在20-40岁(含)以内的前5个员工信息,对查询的结果按年龄升序排序,年龄相同按入职时间升序排序。 +select * from emp where (gender = '男') and (age >= 20 and age <=40) order by age ASC,entrydata DESC limit 5; +``` + +## DCL + +数据控制语言,用来创建数据库用户、控制数据库的访问权限 + +### 用户管理 + +查询用户 + +```sql +use mysql; +select * from user; +``` + +创建用户 + +```sql +CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; +``` + +修改用户密码 + +```sql +ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码'; +``` + +删除用 + +```sql +DROP USER '用户名'@'主机名'; +``` + +例子 + +```sql +-- 创建用户lxh,只能在当前主机localhost访问 +create user 'lxh'@'localhost' identified by '123456'; +-- 创建用户test,能在任意主机访问,使用 % 通配符号 +create user 'lxh'@'%' identified by '123456'; +create user 'lxh' identified by '123456'; +-- 修改密码 +alter user 'lxh'@'localhost' identified with mysql_native_password by '123'; +-- 删除用户 +drop user 'lxh'@'localhost'; +``` + +### 权限管理 + +常见的权限如下,更具体需要百度。 + +| 权限 | 说明 | +| :------------------ | :--------------------- | +| ALL, ALL PRIVILEGES | 所有权限 | +| SELECT | 查询数据 | +| INSERT | 插入数据 | +| UPDATE | 修改数据 | +| DELETE | 删除数据 | +| ALTER | 修改表 | +| DROP | 删除数据库 / 表 / 视图 | +| CREATE | 创建数据库 / 表 | + +```sql +-- 查询权限: +SHOW GRANTS FOR '用户名'@'主机名'; + +-- 授予权限: +GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名'; + +-- 撤销权限: +REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名'; + +-- 注意: + -- 多个权限用逗号分隔 + -- 授权时,数据库名和表名可以用 * 进行通配,代表所有 +``` + +函数是指一段可以直接被另一段程序调用的程序或代码。 + +1. 在企业的 OA 或其他的人力系统中,经常会提供的有这样一个功能,每一个员工登录上来之后都能 够看到当前员工入职的天数。 而在数据库中,存储的都是入职日期,如 2000-11-12,**那如果快速计 算出天数呢?** +2. 在做报表这类的业务需求中, 我们要展示出学员的分数等级分布。而在数据库中,存储的是学生的 分数值,如 98/75,**如何快速判定分数的等级呢?** + +以上这些需求都可以在 MySQL 的函数中得到方便解决。 + +## 字符串函数 + +常用的字符串函数 + +| 函数 | 功能 | +| :------------------------- | :----------------------------------------------------------- | +| CONCAT(s1, s2, …, sn) | 字符串拼接,将 s1, s2, …, sn 拼接成一个字符串 | +| LOWER(str) | 将字符串全部转为小写 | +| UPPER(str) | 将字符串全部转为大写 | +| LPAD(str, n, pad) | 左填充,用字符串 pad 对 str 的左边进行填充,达到 n 个字符串长度 | +| RPAD(str, n, pad) | 右填充,用字符串 pad 对 str 的右边进行填充,达到 n 个字符串长度 | +| TRIM(str) | 去掉字符串头部和尾部的空格 | +| SUBSTRING(str, start, len) | 返回从字符串 str 从 start 位置起的 len 个长度的字符串,起始索引为 1 | + +案例:由于业务需求变更,企业员工的工号,统一为 5 位数,目前不足 5 位数的全部在前面补 0。比如: 1 号员 工的工号应该为 00001。 + +```sql +update emp set workno = LPAD(workno,5,'0'); +``` + +## 数值函数 + +常用的数值函数 + +| 函数 | 功能 | +| :---------- | :----------------------------------- | +| CEIL(x) | 向上取整 | +| FLOOR(x) | 向下取整 | +| MOD(x, y) | 返回 x/y 的模 | +| RAND() | 返回 0~1 内的随机数 | +| ROUND(x, y) | 求参数 x 的四舍五入值,保留 y 位小数 | + +案例:通过数据库的函数,生成一个六位数的随机验证码。 + +```sql +-- 通过rand()获取0~1,乘以1000000,通过四舍五入即可。如果未满6位,则补0 +select lpad(round(rand()*1000000, 0), 6, '0'); +``` + +## 日期函数 + +常用日期函数 + +| 函数 | 功能 | +| :--------------------------------- | :---------------------------------------------------- | +| CURDATE() | 返回当前日期 | +| CURTIME() | 返回当前时间 | +| NOW() | 返回当前日期和时间 | +| YEAR(date) | 获取指定 date 的年份 | +| MONTH(date) | 获取指定 date 的月份 | +| DAY(date) | 获取指定 date 的日期 | +| DATE_ADD(date, INTERVAL expr type) | 返回一个日期 / 时间值加上一个时间间隔 expr 后的时间值 | +| DATEDIFF(date1, date2) | 返回起始时间 date1 和结束时间 date2 之间的天数 | + +案例:查询所有员工的入职天数,并根据入职时间倒叙排序 + +```sql +select name, datediff(curdate(), entrydate) as dates from emp order by dates desc; +``` + +## 流程函数 + +常用流程函数 + +| 函数 | 功能 | +| :----------------------------------------------------------- | :----------------------------------------------------------- | +| IF(value, t, f) | 如果 value 为 true,则返回 t,否则返回 f | +| IFNULL(value1, value2) | 如果 value1 不为空,返回 value1,否则返回 value2 | +| CASE WHEN [val1] THEN [ res1 ] … ELSE [ default ] END | 如果 val1 为 true,返回 res1,… 否则返回 default 默认值 | +| CASE [expr] WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END | 如果 expr 的值等于 val1,返回 res1,… 否则返回 default 默认值 | + +```sql +select + name, + if(age between 14 and 28, '青年','其他') +from emp; +select + name, + (case workaddress when '北京市' then '一线城市' when '上海市' then '一线城市' else '二线城市' end) as '工作地址' +from employee; +``` + +约束是作用于表中字段上的,用于限制存储在表中的数据。目的是保证数据中数据的正确、有效性和完整性。 + +可以在创建表 / 修改表的时候添加约束。 + +常见约束: + +| 约束 | 描述 | 关键字 | +| :----------------------- | :------------------------------------------------------- | :---------------------------------- | +| 非空约束 | 限制该字段的数据不能为 null | NOT NULL | +| 唯一约束 | 保证该字段的所有数据都是唯一、不重复的 | UNIQUE | +| 主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY(自增:AUTO_INCREMENT) | +| 默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT | +| 检查约束(8.0.1 版本后) | 保证字段值满足某一个条件 | CHECK | +| 外键约束 | 用来让两张图的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY | + +## 常用约束 + + + +![img](https://static.7wate.com/img/2022/05/11/58a6b68ad6471.png) + +```sql +create table user( + id int primary key auto_increment comment '主键id', + name varchar(10) not null unique comment '姓名', + age int check(age between 0 and 120) comment '年龄', + status char(1) default '1' comment '状态', + gender char(1) comment '性别' +) comment '用户表'; +``` + +## 外键约束 + +外键约束是用来让两个表的数据之间建立连接,从而保证数据的一致性和完整性。 + + + +![img](https://static.7wate.com/img/2022/05/11/110dd22cdc2cf.png) + + + +```sql +-- 在创建表时添加外键 +CREATE TABLE 表名( + 字段名 字段类型, + ... + [CONSTRAINT] [外键名称] FOREIGN KEY(外键字段名) REFERENCES 主表(主表列名) +); +-- 为表补上外键 +ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名); +-- 删除外键 +ALTER TABLE 表名 DROP FOREIGN KEY 外键名; +``` + +案例:为 emp 表补上外键,dept_id 关联 dep 中的 id + +```sql +alter table emp add constraint fk_emp_dept_id foreign key dept_id references dep(id); +``` + +## 删除 / 更新行为 + +表添加了外键之后,再删除父表数据时产生的约束行为,我们就称为删除 / 更新行为。有以下常见的几种: + +| 行为 | 说明 | +| :---------- | :----------------------------------------------------------- | +| NO ACTION | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除 / 更新(与 RESTRICT 一致) | +| RESTRICT | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除 / 更新(与 NO ACTION 一致) | +| CASCADE | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除 / 更新外键在子表中的记录 | +| SET NULL | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为 null(要求该外键允许为 null) | +| SET DEFAULT | 父表有变更时,子表将外键设为一个默认值(Innodb 不支持) | + +```sql +ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名(主表字段名) ON UPDATE 行为 ON DELETE 行为; +-- 其实就是在添加外键时的多一个定义 +-- 例如:在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除/更新外键在子表中的记录 +alter table emp add constraint fk_emp_dept_id foreign key dept_id references dep(id) on update cascade on delete cascade; +``` + +项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种: + +## 多表关系 + +### 一对多 + +案例:部门与员工 + +关系:一个部门对应多个员工,一个员工对应一个部门 + +实现:在多的一方建立外键,指向一的一方的主键 + + + +![img](https://static.7wate.com/img/2022/05/11/0f32732cb6a00.png) + + + +### 多对多 + +案例:学生与课程 + +关系:一个学生可以选多门课程,一门课程也可以供多个学生选修 + +实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键 + + + +![img](https://static.7wate.com/img/2022/05/11/c029ca35af10d.png) + + + +### 一对一 + +案例:用户与用户详情 + +关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率 + +实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE) + + + +![img](https://static.7wate.com/img/2022/05/11/ae2c3633fde99.png) + + + +## 多表查询 + +指的是从多表中查询出想要的数据。 + +**笛卡尔积**:笛卡尔乘积是指在数学中,两个集合 A 集合和 B 集合的所有组合情况。(在多表查询时,需要消除无效的笛 +卡尔积) + +例如:使用 `select * from employee, dept;` 查询出来的结果是两个表的乘积。 + +**消除笛卡尔积**:`select * from employee, dept where employee.dept = dept.id;` + + + +![img](https://static.7wate.com/img/2022/05/11/eaa68daf10655.png) + + + +在进行多表查询测试之前,我们先准备好数据表。 + +```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); +``` + +### 内连接查询 + +内连接查询的是两张表交集的部分。 + + + +![img](https://static.7wate.com/img/2022/05/11/8d8fa0226c444.png) + + + +隐式内连接: +`SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;` + +显式内连接: +`SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ...;` + +**显式性能比隐式高** + +```sql +-- 查询每一个员工的姓名,及关联的部门的名称(隐式内连接查询) +select emp.name, dept.name from emp, dep where emp.dept_id = dept.id; +select e.name, d.name from emp e, dep d where e.dept_id = d.id; +-- 查询每一个员工的姓名,及关联的部门的名称(显式内连接) +select emp.name, dept.name from emp inner join dept on emp.dept_id = dept.id; +select e.name, d.name from emp e inner join dept d on e.dept_id = d.id; +``` + +### 外连接查询 + +```sql +-- 查询emp表中的所有数据和对应的部门信息(左外连接) +select e.*, d.* from emp e left join dept d on e.dept_id = d.id; +-- 查询dept表中的所有数据和对应的员工信息(右外连接) +select d.name, e.* from emp e right join dept d on e.dept_id = d.id; +-- 以上可以看到,左连接可以查询到没有dept的employee,右连接可以查询到没有employee的dept +``` + +### 自连接查询 + +自连接查询指的是于自身的连接查询,把自身当作另一个表,所以要求**必须使用表别名**,**自连接查询可以是内连接查询或者外连接查询。** + +```sql +-- 查询员工及其领导 +select a.name '员工', b.name '领导' from emp a, emp b where a.managerid = b.id; +-- 查询所有员工emp及其领导的名字,如果员工没有领导,也需要查询出来 +select a.*, b.name '领导' from emp a left join emp b on a.managerid = b.id; +``` + +### 联合查询 union + +联合查询就是把多次查询的结果合并,形成一个新的查询集。**联合查询比使用 or 效率高,不会使索引失效。**对于联合查询,多张表的列数必须保持一致,字段类型也必须保持一段。union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重。 + +```sql +SELECT 字段列表 FROM 表A ... +UNION [ALL] +SELECT 字段列表 FROM 表B ... +-- 将薪资低于5000的员工,和年龄大于50岁的员工全部查询出来. +select * from emp where salary < 5000 +union +select * from emp where age > 50 +-- 使用 UNION ALL 会有重复结果,UNION 则不会。 +``` + +## 子查询 + +SQL 语句中嵌套 SELECT 语句,称谓嵌套查询,又称子查询。 + +```sql +SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2); +``` + +**子查询外部的语句可以是 INSERT / UPDATE / DELETE / SELECT 的任何一个** + +根据子查询结果可以分为: + +- 标量子查询(子查询结果为单个值) +- 列子查询(子查询结果为一列) +- 行子查询(子查询结果为一行) +- 表子查询(子查询结果为多行多列) + +根据子查询位置可分为: + +- WHERE 之后 +- FROM 之后 +- SELECT 之后 + +### 标量子查询 + +子查询返回的结果是单个值(数字、字符串、日期等)。 + +常用操作符:`- < > > >= < <=` + +```sql +-- 查询销售部所有员工 +select id from dept where name = '销售部'; +-- 根据销售部部门ID,查询员工信息 +select * from emp where dept = 4; +-- 合并成标量子查询 +select * from emp where dept = (select id from dept where name = '销售部'); +-- 查询李白入职以后的新入职员工信息 +select * from emp where entrydate > (select entrydate from employee where name = '李白'); +``` + +### 列子查询 + +返回的结果是一列(可以是多行)。 + +常用操作符: + +| 操作符 | 描述 | +| :----- | :------------------------------------------ | +| IN | 在指定的集合范围内,多选一 | +| NOT IN | 不在指定的集合范围内 | +| ANY | 子查询返回列表中,有任意一个满足即可 | +| SOME | 与 ANY 等同,使用 SOME 的地方都可以使用 ANY | +| ALL | 子查询返回列表的所有值都必须满足 | + +```sql +-- 查询销售部和市场部的所有员工信息 +select * from emp where dept_id in(select id from dept where name = '销售部' or name = '市场部'); +-- 查询比财务部所有人工资都高的员工信息 +select * from emp where salary > all(select salary from emp where dept_id = (select id from dept where name = '财务部')); +-- 查询比研发部任意一人工资高的员工信息 +select * from emp where salary > any(select salary from emp where dept_id = (select id from dept where name = '研发部')); +``` + +### 行子查询 + +返回的结果是一行(可以是多列)。 + +常用操作符:`=, <, >, IN, NOT IN` + +```sql +-- 查询与李白的薪资及直属领导相同的员工信息 +select * from emp where (salary, manager) = (12500, 1); +select * from emp where (salary, manager) = (select salary, manager from emp where name = '李白'); +``` + +### 表子查询 + +返回的结果是多行多列 +常用操作符:IN + +```sql +-- 查询与xxx1,xxx2的职位和薪资相同的员工 +select * from emp where (job, salary) in (select job, salary from emp where name = 'xxx1' or name = 'xxx2'); +-- 查询入职日期是2022-01-01之后的员工,及其部门信息 +select e.*, d.* from (select * from emp where entrydate > '2022-01-01') as e left join dept as d on e.dept_id = d.id; +``` + +## 多表练习 + +数据准备: + +```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); + +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 练习 + +以显式内连接为主 + +```sql +-- 1. 查询员工的姓名、年龄、职位、部门信息 (隐式内连接) +select e.name, e.age, e.job, d.name from emp e, dept d where e.dept_id = d.id; +-- 2. 查询年龄小于30岁的员工的姓名、年龄、职位、部门信息(显式内连接) +select e.name, e.age, e.job, d.name from emp e inner join dept d on e.dept_id = d.id where e.age < 30; +-- 3. 查询拥有员工的部门ID、部门名称 + -- 使用内连接可以让空的数据过滤 + -- 使用 distinct 可以过滤相同数据 +select distinct d.id, d.name from dept d inner join emp e on d.id = e.dept_id; +-- 4. 查询所有年龄大于40岁的员工, 及其归属的部门名称; 如果员工没有分配部门, 也需要展示出来 +select e.*, d.name from emp e left join dept d on e.dept_id = d.id where e.age > 40; +-- 5. 查询所有员工的工资等级 +select e.name, e.salary, s.grade from emp e inner join salgrade s on e.salary between s.losal and s.hisal; +-- 6. 查询 "研发部" 所有员工的信息及工资等级 +select d.name '部门', e.*, s.grade from emp e inner join salgrade s on e.salary between s.losal and s.hisal inner join dept d on e.dept_id = d.id where d.name = '研发部'; +-- 7. 查询 "研发部" 员工的平均工资 +select d.name '部门', avg(e.salary) from emp e inner join dept d on e.dept_id = d.id where d.name = '研发部'; +-- 8. 查询工资比 "灭绝" 高的员工信息。 +select * from emp where salary > (select salary from emp where name = '灭绝'); +-- 9. 查询比平均薪资高的员工信息 +select * from emp where salary > (select avg(emp.salary) from emp); +-- 10. 查询低于本部门平均工资的员工信息 +select * from emp e2 where e2.salary < (select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id ); +-- 11. 查询所有的部门信息, 并统计部门的员工人数 +select d.*, (select count(*) from emp e where e.dept_id = d.id) '人数' from dept d; +``` + + + +![img](https://static.7wate.com/img/2022/05/11/7ee27cf278dd9.png) + +事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求 + +**这些操作要么同时成功,要么同时失败。** + +默认 MysQL 的事务是自动提交的,当执行一条 DML 语句,MySQL 会立即隐式地提交事务。 + +## 事务操作 + +事务演示如下,如果 2-3 步骤之间报错终端,那么张三就丢了 1000;而李四没收到 1000; + +```sql +-- 1. 查询张三账户余额 +select * from account where name = '张三'; +-- 2. 将张三账户余额 -1000 +update account set money = money - 1000 where name = '张三'; +-- 3. 将李四账户余额 +1000 +update account set money = money + 1000 where name = '李四'; +``` + +因此我们需要把上述代码整合成一个事务,完全执行后才提交。 + +```sql +-- 查看事务提交方式 +SELECT @@AUTOCOMMIT; +-- 设置事务提交方式,1 为自动提交,0 为手动提交,该设置只对当前会话有效 +SET @@AUTOCOMMIT = 0; +-- 提交事务 +COMMIT; +-- 回滚事务 +ROLLBACK; +``` + +第二种方式 + +```sql +-- 开启事务: +START TRANSACTION 或 BEGIN TRANSACTION; +-- 提交事务: +COMMIT; +-- 回滚事务: +ROLLBACK; +``` + +调整事务之后,重新运行 sql + +```sql +-- 1. 查询张三账户余额 +select * from account where name = '张三'; +-- 2. 将张三账户余额 -1000 +update account set money = money - 1000 where name = '张三'; +-- 3. 将李四账户余额 +1000 +update account set money = money + 1000 where name = '李四'; +commit; +``` + +## 事务四大特性 + +四大特性 ACID: + +- 原子性 (Atomicity):事务是不可分割的最小操作但愿,要么全部成功,要么全部失败。 +- 一致性 (Consistency):事务完成时,必须使所有数据都保持一致状态。 +- 隔离性 (Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。 +- 持久性 (Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。 + +## 并发事务问题 + +| 问题 | 描述 | +| :--------- | :----------------------------------------------------------- | +| 脏读 | 一个事务读到另一个事务还没提交的数据 | +| 不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同 | +| 幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在 | + +### 脏读 + +如下图,事务 A 更新 1,但还未提交,此时被事务 B 查去了,这就导致可能事务 A 最终决定不提交,但是事务 B 拿出来后当真了,所以这种现象叫做脏读。 + + + +![img](https://static.7wate.com/img/2022/05/11/85c311e17a51d.png) + + + +### 不可重复读 + +如下图,事务 A 查询第一次后,事务 B 更新了这条数据,事务 A 查询第二次时发现跟第一次查询的结果不一样,这种现象叫做不可重复读。 + + + +![img](https://static.7wate.com/img/2022/05/11/0d298382a22d4.png) + + + +### 幻读 + +幻读是在解决 [不可重复读] 的基础上产生的新问题,如下图,事务 A 读取 id 为 1 的数据为空,事务 B 插入 id 为 1 的数据,之后事务 A 想要插入这条数据发现插入不了(比如被主键约束了),然后事务 A 重新查询还是找不到 id 为 1 的数据(因为我们解决了[不可重复读],所以查询出来的结果跟第一次查是一致的)。 + + + +![img](https://static.7wate.com/img/2022/05/11/b5bdbc0d6b4e0.png) + + + +## 事务隔离级别 + +MySQL 默认的事务隔离级别是 Repeatable Read + +Oracle 默认的事务隔离级别是 Read committed + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | +| :-------------------- | :--- | :--------- | :--- | +| Read uncommitted | √ | √ | √ | +| Read committed | × | √ | √ | +| Repeatable Read(默认) | × | × | √ | +| Serializable | × | × | × | + +√ 表示在当前隔离级别下该问题会出现, + +Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差。 + +```sql +-- 查看事务隔离级别: +SELECT @@TRANSACTION_ISOLATION; +-- 设置事务隔离级别: +SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }; +-- SESSION 是会话级别,表示只针对当前会话有效 +-- GLOBAL 表示对所有会话有效 +``` + + + +>版权属于:乐心湖's Blog +> +>本文链接:https://www.xn2001.com/archives/677.html \ No newline at end of file