From 2e0b923eee72e6f658e3f1e132e1b958683eda81 Mon Sep 17 00:00:00 2001 From: 7Wate Date: Wed, 31 Aug 2022 20:23:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=EF=BC=9AMySQL=20=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wiki/dev/MySQL/SQL 函数.md | 102 +++ wiki/dev/MySQL/SQL 语句.md | 507 +++++++++++++++ wiki/dev/MySQL/数据库事务.md | 127 ++++ wiki/dev/MySQL/数据库查询.md | 350 +++++++++++ wiki/dev/MySQL/数据库概述.md | 38 ++ wiki/dev/MySQL/数据库规范.md | 256 ++++++++ wiki/dev/MySQL/数据表约束.md | 87 +++ wiki/dev/SQL/基础.md | 1150 ---------------------------------- 8 files changed, 1467 insertions(+), 1150 deletions(-) create mode 100644 wiki/dev/MySQL/SQL 函数.md create mode 100644 wiki/dev/MySQL/SQL 语句.md create mode 100644 wiki/dev/MySQL/数据库事务.md create mode 100644 wiki/dev/MySQL/数据库查询.md create mode 100644 wiki/dev/MySQL/数据库概述.md create mode 100644 wiki/dev/MySQL/数据库规范.md create mode 100644 wiki/dev/MySQL/数据表约束.md delete mode 100644 wiki/dev/SQL/基础.md diff --git a/wiki/dev/MySQL/SQL 函数.md b/wiki/dev/MySQL/SQL 函数.md new file mode 100644 index 00000000..4b2a686a --- /dev/null +++ b/wiki/dev/MySQL/SQL 函数.md @@ -0,0 +1,102 @@ +--- +title: SQL 函数 +description: SQL 函数 +keywords: +- 数据库 +- 函数 +tags: +- SQL +sidebar_position: 3 +author: 7Wate +date: 2022-08-31 +--- +## 函数 + +函数是指一段可以直接被另一段程序调用的程序或代码。 + +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; +``` diff --git a/wiki/dev/MySQL/SQL 语句.md b/wiki/dev/MySQL/SQL 语句.md new file mode 100644 index 00000000..5338e235 --- /dev/null +++ b/wiki/dev/MySQL/SQL 语句.md @@ -0,0 +1,507 @@ +--- +title: SQL 语句 +description: SQL 语句 +keywords: +- SQL +- 语句 +tags: +- SQL +sidebar_position: 2 +author: 7Wate +date: 2022-08-31 +--- +## 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 '用户名'@'主机名'; + +-- 注意: + -- 多个权限用逗号分隔 + -- 授权时,数据库名和表名可以用 * 进行通配,代表所有 +``` diff --git a/wiki/dev/MySQL/数据库事务.md b/wiki/dev/MySQL/数据库事务.md new file mode 100644 index 00000000..ff05083b --- /dev/null +++ b/wiki/dev/MySQL/数据库事务.md @@ -0,0 +1,127 @@ +--- +title: 数据库事务 +description: 数据库事务 +keywords: +- 数据库 +- 事务 +tags: +- SQL +sidebar_position: 6 +author: 7Wate +date: 2022-08-31 +--- +事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求 + +**这些操作要么同时成功,要么同时失败。** + +默认 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 表示对所有会话有效 +``` diff --git a/wiki/dev/MySQL/数据库查询.md b/wiki/dev/MySQL/数据库查询.md new file mode 100644 index 00000000..24ff2ef6 --- /dev/null +++ b/wiki/dev/MySQL/数据库查询.md @@ -0,0 +1,350 @@ +--- +title: 数据库查询 +description: 数据表数据查询 +keywords: +- 数据库 +- 查询 +tags: +- SQL +sidebar_position: 5 +author: 7Wate +date: 2022-08-31 +--- +## 多表关系 + +项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:一对一、一对多、多对多 + +### 一对一 + +案例:用户与用户详情 + +关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率 + +实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE) + +![img](https://static.7wate.com/img/2022/05/11/ae2c3633fde99.png) + +### 一对多 + +案例:部门与员工 + +关系:一个部门对应多个员工,一个员工对应一个部门 + +实现:在多的一方建立外键,指向一的一方的主键 + +![img](https://static.7wate.com/img/2022/05/11/0f32732cb6a00.png) + +### 多对多 + +案例:学生与课程 + +关系:一个学生可以选多门课程,一门课程也可以供多个学生选修 + +实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键 + +![img](https://static.7wate.com/img/2022/05/11/c029ca35af10d.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**显式性能比隐式高** + +```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) diff --git a/wiki/dev/MySQL/数据库概述.md b/wiki/dev/MySQL/数据库概述.md new file mode 100644 index 00000000..428ac3a8 --- /dev/null +++ b/wiki/dev/MySQL/数据库概述.md @@ -0,0 +1,38 @@ +--- +title: 数据库概述 +description: 数据库概述 +keywords: +- 数据库 +- 概述 +tags: +- 数据库 +sidebar_position: 1 +author: 7Wate +date: 2022-08-31 +--- +## 数据库 + +| 名称 | 全称 | 简称 | +| :------------- | :----------------------------------------------------------- | :-------------------------------- | +| 数据库 | 存储数据的仓库,数据是有组织的进行存储 | 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) diff --git a/wiki/dev/MySQL/数据库规范.md b/wiki/dev/MySQL/数据库规范.md new file mode 100644 index 00000000..15c0b20d --- /dev/null +++ b/wiki/dev/MySQL/数据库规范.md @@ -0,0 +1,256 @@ +--- +title: 数据库规范 +description: 数据库规范 +keywords: +- 数据库 +- 规范 +tags: +- SQL +sidebar_position: 7 +author: 7Wate +date: 2022-08-31 +--- + +## 命名 + +建表时统一规范**表**、**字段**和**索引**的命名,能够提高沟通效率和降低维护成本。 + +### 见名知意 + +#### 反例 + +```sql +-- 用户名字段 +yong_hu_ming +用户_name +name +user_name_123456789 +``` + +#### 正例 + +```sql +-- 用户名字段 +user_name +``` + +***字段不宜过长,尽量控制在 30 个字符以内。*** + +### 大小写 + +**统一采用小写字母;**因为从视觉上小写字母更容易让人读懂,所以采用小写字母。 + +**反例:** + +```sql +-- 字段名 +PRODUCT_NAME +PRODUCT_name +``` + +**正例:** + +```sql +-- 字段名 +product_name +``` + +### 分隔符 + +多个单词之间使用**半角下划线作为分隔符**。 + +#### 反例 + +```sql +-- 字段名 +productname +productName +product name +product@name +``` + +**正例:** + +```sql +-- 字段名 +product_name +``` + +### 表名 + +对于表名,在见名知意的基础之上带上**前缀**;以便于归类区分,同时避免出现同名表。 + +```sql +-- 业务表 +order_pay +order_pay_detail +-- 商品表 +product_spu +product_sku +``` + +### 字段名称 + +数据表内常用**公共字段采用统一命名**。 + +```sql +-- 状态字段 +status +-- 创建时间、修改时间 +create_time +update_time +-- 外键 +表名_字段 +``` + +### 索引名 + +在数据库中,索引有主键、普通索引、唯一索引、联合索引等。 + +- 表主键:一般使用 **id** 命名。 + +- 普通索引和联合索引: **ix_** 前缀。 + +- 唯一索引: **ux_** 前缀。 + +## 字段类型 + +- 尽可能选择占用存储空间小的字段类型,在满足正常业务需求的情况下,从小到大,往上选。 +- 如果字符串长度固定,或者差别不大,可以选择 char 类型;如果字符串长度差别较大,可以选择varchar类型。 +- 是否字段:建议选择 bit 类型。 +- 枚举字段:建议选择 tinyint 类型。 +- 主键字段:建议选择 bigint 类型。 +- 金额字段:建议选择 decimal 类型。 +- 时间字段:建议选择 timestamp 或 datetime 类型。 + +## 字段长度 + +在 MySQL 中除了 **varchar 和 char 是字符长度**之外,**其余的类型都是字节长度**。 + +例如 bigint(4)实际长度是 8 个字节。 + +## 字段个数 + +数据表的字段**尽量不要超过 20 个。** + +特殊情况可以将**一张表拆成多张表**形成组合表,其主键相同。 + +## 主键 + +**数据表必须拥有主键**,因为主键拥有自带索引并且不需要回表,所以相比于其他索引查询效率最高。 + +数据表**主键建议保存跟业务无关的值**,减少业务耦合性方便扩展。 + +单体数据库中,主键可以设置 **AUTO_INCREMENT(自动增长)**。 + +分布式数据库中,尤其是分库分表的业务库中,主键最好由外部算法(比如:雪花算法)生成,并且保证生成的 id 是全局唯一的。 + +## 存储引擎 + +建议在使用 MySQL8 以后的版本时,使用默认的 innodb 存储引擎,无需修改存储引擎。 + +## NOT NULL + +在定义字段时,应该尽可能明确该字段 NOT NULL。 + +主要有以下原因: + +1. 在 innodb 中,需要额外的空间存储 null 值,需要占用更多的空间。 +2. null 值可能会导致索引失效。 +3. null 值只能用 is null 或者 is not null 判断,用等于号判断永远返回 False。 + +## 外键 + +外键主要作用是保证数据的**一致性**和**完整性**。 + +```sql +create table class ( + id int(10) primary key auto_increment, + cname varchar(15) +); +``` + +```sql +create table student( + id int(10) primary key auto_increment, + name varchar(15) not null, + gender varchar(10) not null, + cid int, + foreign key(cid) references class(id) +); +``` + +上述两个数据表 class 和 student 中,如果直接删除 student 表的 行数据,因为存在外键关联,所以就会报异常。必须要先删除 class 表对应的 student 表中 cid 那行数据,再删除 student 表的数据才能够**保证数据的一致性和完整性。** + +只有存储引擎是innodb时,才能使用外键。如果**注重性能的场景下,建议不使用外键、存储过程和触发键**。 + +## 索引 + +创建数据表时,不仅可以指定主键索引,还**可以创建普通索引**;建议单个数据表的普通索引不要超过 5 个。 + +```sql +create table product_sku ( + id int(10) primary key auto_increment, + spu_id int(10) not null, + brand_id int(10) not null, + name varchar(15) not null, + KEY `ix_spu_id` (`spu_id`) USING BTREE, + KEY `ix_brand_id` (`brand_id`) USING BTREE +); +``` + +数据表创建时,添加 ix_spu_id、ix_brand_id 普通索引,可以提高检索数据效率。 + +## 时间字段 + +目前 MySQL 支持 date、datetime、timestamp、varchar 等时间类型,**建议使用 datetime**。 + +- varchar:存储时间的类型是 String,无法创建索引。 + +- date:适用于保存日期,比如:2020-08-20 12:12:20。 + +- timestamp:4 个字节,取值范围为 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07,受时区影响。 +- datetime:8个字节,取值范围为 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59,不受时区影响。 + +## 金额字段 + +MySQL 中 float、double、decimal 等支持浮点数。 + +因为 float 和 double 可能会丢失精度,所以建议使用 decimal 类型保存金额数值。 + +定义浮点数 decimal(m,n),n 是小数的长度,m 是整数和小数的总长度。 + +## 唯一索引 + +唯一索引支持单个字段、多个字段联合。 + +如果多个字段联合的唯一索引字段值出现 null,那么唯一性约束可能会失效。 + +## 字符集 + +MySQL 中常用字符集的有 latin1、gbk 、utf-8、utf8mb4 等,**建议使用 utf-8 字符集**。 + +| 字符集 | 长度 | 功能 | 备注 | +| ------- | ---- | ------------------------------ | --------------------------------------- | +| latin1 | 1 | 默认字符集 | 容易出现乱码,使用较少 | +| gbk | 2 | 支持中文,非国际通用字符 | 不支持国际通用字符,使用不多 | +| utf-8 | 3 | 支持中英文混合,国际通用字符集 | 相对 utf8mb4 占用存储更少,不支持 emoji | +| utf8mb4 | 4 | 兼容 UTF-8 | 4 个字节支持存储更多字符,支持 emoji | + +## 排序规则 + +数据表创建时 COLLATE 参数可以设置排序规则,排序规则与字符集相关。 + +数据表使用 utf8mb4 时字符排序规则以 utf8mb4_ 开头的,常用排序规则 utf8mb4_general_ci 、utf8mb4_bin 等。 + +- utf8mb4_general_ci:**不区分字母大小**。 + +- utf8mb4_bin:**区分字母大小**。 + +建议字符排序规则务必根据实际的业务场景选择,否则容易出现不可预测的问题。 + +## 大字段 + +大字段即占用较多存储空间的字段,**建议大字段定义成 varchar 类型**。 + +例如用户的评论就属于大字段,但是字段长度不唯一,所以一般会限制总长度。 diff --git a/wiki/dev/MySQL/数据表约束.md b/wiki/dev/MySQL/数据表约束.md new file mode 100644 index 00000000..554451d3 --- /dev/null +++ b/wiki/dev/MySQL/数据表约束.md @@ -0,0 +1,87 @@ +--- +title: 数据表约束 +description: 数据表行数据约束 +keywords: +- 数据库 +- 约束 +tags: +- SQL +sidebar_position: 4 +author: 7Wate +date: 2022-08-31 +--- + +## 数据约束 + +约束是作用于表中字段上的,用于限制存储在表中的数据。目的是保证数据中数据的正确、有效性和完整性。 + +可以在创建表 / 修改表的时候添加约束。 + +常见约束: + +| 约束 | 描述 | 关键字 | +| :----------------------- | :------------------------------------------------------- | :---------------------------------- | +| 非空约束 | 限制该字段的数据不能为 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; +``` diff --git a/wiki/dev/SQL/基础.md b/wiki/dev/SQL/基础.md deleted file mode 100644 index 933bad48..00000000 --- a/wiki/dev/SQL/基础.md +++ /dev/null @@ -1,1150 +0,0 @@ ---- -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**显式性能比隐式高** - -```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 -> ->本文链接: