mysql基础学习笔记

文章目录

  • 一、最基本的SELECT语句
    • 1. SQL语言的规则和规范
      • 1) 基本规则
      • 2) SQL大小写规范
      • 3) 注释
      • 4) 命名规则
    • 2.SQL语言分类
    • 3. 基本的SELECT语句
      • 1) SELECT ... FROM
      • 2) 列的别名
      • 3) 去除重复行
      • 4) 空值参与运算
      • 5) 着重号 ``
      • 6) 查询常数
    • 4. 显示表结构
    • 5. 过滤数据
  • 二、运算符
    • 1. 算术运算符
    • 2. 比较运算符
      • 1) 等号运算符
      • 2) 不等于运算符
      • 3) 空运算符
      • 4) 非空运算符
      • 5) 最小值运算符
      • 6) 最大值运算符
      • 7) BETWEEN AND运算符
      • 8) IN运算符
      • 9) NOT IN运算符
      • 10) LIKE运算符
      • 11) REGEXP运算符
    • 3. 逻辑运算符
    • 4. 位运算
    • 5. 运算符的优先级
    • 6.扩展:使用正则表达式查询
  • 三、排序与分页
    • 1. 排序规则
      • 1) 单列排序
      • 2) 多列排序
    • 2. 分页
  • 四、多表查询
    • 1. 多表查询分类讲解
      • 1) 自连接
      • 2) 内连接与外连接
    • 2. UNION的使用
    • 3.七种SQL JOINS的实现
    • 4. SQL99语法的新特性
      • 1) 自然连接
      • 2) USING连接
    • 5. 小结
  • 五、单行函数
    • 1. 数值函数
      • 1) 基本函数
      • 2) 角度与弧度互换函数
      • 3) 三角函数
      • 4) 指数与对数函数
      • 5) 进制间的转换
    • 2. 字符串函数
    • 3. 日期和时间函数
      • 1) 获取日期、时间
      • 2) 日期与时间戳的转换
      • 3) 获取月份、星期、星期数、天数等函数
      • 4) 日期的操作函数
      • 5) 时间和秒钟转换的函数
      • 6) 计算日期和时间的函数
      • 7) 日期的格式化与解析
    • 4. 流程控制函数
    • 5. 加密与解密函数
    • 6. MySQL信息函数
  • 六、聚合函数
    • 1. 聚合函数介绍
      • 1) AVG和SUM函数
      • 2) MIN和MAX函数
      • 3) COUNT函数
    • 2. GROUP BY
      • 1) 基本使用
      • 2) 使用WITH ROLLUP
    • 3. HAVING
      • 1) 基本使用
      • 2) WHERE和HAVING的对比
    • 4. SELECT的执行过程
      • 1) 查询的结构
      • 2) SQL的执行原理
  • 七、子查询
    • 1. 基本使用
    • 2. 子查询的分类
    • 3. 单行子查询
      • 1) 单行比较操作符
      • 2) 代码示例
      • 3) CASE中的子查询
      • 4) 子查询中的空值问题
      • 5) 非法使用子查询
    • 4. 多行子查询
      • 1) 多行比较操作符
      • 2) 示例
      • 3) 空值问题
    • 5. 相关子查询
      • 1) 代码示例
      • 2) EXISTS 与 NOT EXISTS 关键字
      • 3) 相关更新
      • 4) 相关删除
    • 6. 思考题
    • 7. 练习
  • 八、创建和管理表
    • 1. 基础知识
      • 1) 标识符命名规则
      • 2) MySQL中的数据类型
    • 2. 创建和管理数据库
      • 1) 创建数据库
      • 2) 使用数据库
      • 3) 修改数据库
    • 3. 创建表
      • 1) 创建方式1
      • 2) 创建方式2
      • 3) 查看数据表结构
    • 4. 修改表
      • 1) 追加一个列
      • 2) 修改一个列
      • 3) 重命名一个列
      • 4) 删除一个列
      • 5) 更改表名
    • 5. 删除表
    • 6. 清空表
  • 九、数据处理之增删改
    • 1. 插入数据
      • 1) 方式1:VALUES的方式添加
      • 2) 方式2:将查询结果插入到表中
    • 2. 更新数据
    • 3. 删除数据
    • 4. MySQL8新特性:计算列
  • 十、MySQL数据类型
    • 1. MySQL中的数据类型
    • 2. 整数类型
      • 1) 类型介绍
      • 2) 可选属性
      • 3) 适用场景
    • 3. 浮点类型
      • 1) 类型介绍
    • 4. 日期与时间类型
    • 7. 文本字符串类型
    • 8. ENUM类型
    • 9. SET类型
  • 十一、约束
    • 1. 约束的分类
    • 2. 非空约束
      • 1) 作用
      • 2) 关键字
      • 3) 特点
      • 4) 添加非空约束
      • 5) 删除非空约束
    • 3. 唯一性约束
      • 1) 作用
      • 2) 关键字
      • 3) 特点
      • 4) 添加唯一约束
      • 5) 关于复合唯一约束
      • 6) 删除唯一约束
    • 4. PRIMARY KEY 约束
      • 1) 作用
      • 2) 关键字
      • 3) 特点
      • 4) 添加主键约束
      • 5) 关于复合主键
      • 6) 删除主键约束
    • 5. 自增列:AUTO_INCREMENT
      • 1) 作用
      • 2) 关键字
      • 3) 特点
      • 4) 如何指定自增约束
      • 5) 删除自增约束
    • 6. FOREIGN KEY 约束
      • 1) 作用
      • 2) 关键字
      • 3) 主表和从表/父表和子表
      • 4) 特点
      • 5) 添加外键约束
      • 6) 约束等级
      • 7) 删除外键约束
    • 7. CHECK 约束
      • 1) 作用
      • 2) 关键字
      • 3) 说明
    • 8. DEFAULT约束
      • 1) 作用
      • 2) 关键字
      • 3) 添加默认值
    • 9. 面试
  • 十二、视图
    • 1. 常见的数据库对象
    • 2. 视图概述
    • 3. 创建视图
      • 1) 创建单表视图
      • 2) 创建多表联合视图
      • 3) 基于视图创建视图
    • 4. 查看视图
    • 5. 更新视图的数据
      • 1) 一般情况
      • 2) 不可更新的视图
    • 6. 修改、删除视图
      • 1) 修改视图
      • 2) 删除视图
    • 7. 总结
      • 1) 优点
      • 2) 不足
  • 十三、存储过程与存储函数
    • 1. 存储过程(Stored Procedure)概述
      • 1) 理解
      • 2) 分类
    • 2. 创建存储过程
      • 1) 语法分析
      • 2) 代码举例
    • 3. 调用存储过程
      • 1) 调用格式
      • 2) 代码举例
    • 4. 存储函数的使用
      • 1) 语法分析
      • 2) 调用存储函数
      • 3) 代码举例
      • 4) 对比存储函数与存储过程
    • 5. 存储过程和函数的查看、修改、删除
      • 1) 查看
      • 2) 修改
      • 3) 删除
    • 6. 关于存储过程使用的争议
      • 1) 优点
      • 2) 缺点
  • 十四、变量、流程控制与游标
    • 1. 变量
      • 1) 系统变量
      • 2) 用户变量
    • 2. 定义条件与处理程序
      • 1) 案例分析
      • 2) 定义条件
      • 3) 定义处理程序
      • 4) 案例解决
    • 3. 流程控制
      • 1) 分支结构之 IF
      • 2) 分支结构之 CASE
      • 3) 循环结构之LOOP
      • 4) 循环结构之WHILE
      • 5) 循环结构之REPEAT
      • 6) 跳转语句之LEAVE语句
      • 7) 跳转语句之ITERATE语句
    • 4. 游标
      • 1) 游标(光标)概念
      • 2) 使用游标步骤
      • 3) 举例
      • 4) 小结
    • 补充:MySQL 8.0的新特性—全局变量的持久化
  • 十五、触发器
    • 1. 触发器概述
    • 2. 触发器的创建
      • 1) 语法
      • 2) 举例
    • 3. 查看、删除触发器
      • 1) 查看触发器
      • 2) 删除触发器
    • 4. 触发器的优缺点
      • 1) 优点
      • 2) 缺点
      • 3) 注意
  • 十六、MySQL8其他新特性
    • 1. MySQL8新特性概述
      • 1) MySQL8.0 新增特性
    • 2. 新特性1:窗口函数
      • 1) 使用窗口函数前后对比
      • 2) 窗口函数分类
      • 3) 语法结构
      • 4) 分类讲解
        • 1) 序号函数
        • 2) 分布函数
        • 3) 前后函数
        • 4) 首尾函数
        • 5) 其他函数
      • 5) 小结
    • 3. 新特性2:公用表表达式
      • 1) 普通公用表表达式
      • 2) 递归公用表表达式
      • 3) 小结

一、最基本的SELECT语句

1. SQL语言的规则和规范

1) 基本规则

  • SQL 可以写在一行或者多行。为了提高可读性,各子句分行写,必要时使用缩进
  • 每条命令以 ; 或 \g 或 \G 结束
  • 关键字不能被缩写也不能分行
  • 关于标点符号
    • 必须保证所有的()、单引号、双引号是成对结束的
    • 必须使用英文状态下的半角输入方式
    • 字符串型和日期时间类型的数据可以使用单引号(’ ')表示
    • 列的别名,尽量使用双引号(" "),而且不建议省略as

2) SQL大小写规范

  • MySQL 在 Windows 环境下是大小写不敏感的
  • MySQL 在 Linux 环境下是大小写敏感的
    • 数据库名、表名、表的别名、变量名是严格区分大小写的
    • 关键字、函数名、列名(或字段名)、列的别名(字段的别名) 是忽略大小写的。
  • 推荐采用统一的书写规范:
    • 数据库名、表名、表别名、字段名、字段别名等都小写
    • SQL 关键字、函数名、绑定变量等都大写

3) 注释

单行注释:#注释文字(MySQL特有的方式)
单行注释:-- 注释文字(--后面必须包含一个空格。)
多行注释:/* 注释文字 */

4) 命名规则

  • 数据库、表名不得超过30个字符,变量名限制为29个
  • 必须只能包含 A–Z, a–z, 0–9, _共63个字符
  • 数据库名、表名、字段名等对象名中间不要包含空格 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;
  • 同一个表中,字段不能重名 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
  • 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据 类型在一个表里是整数,那在另一个表里可就别变成字符型了

2.SQL语言分类

  1. Data Definition Language (DDL 数据定义语言)
    1. 用来定义数据库对象:数据库、表、列等
    2. 关键字:createdropalter
  2. Data Manipulation Language(DML 数据操纵语言)
    1. 用来对数据库中标的数据进行增删改
    2. 关键字:inertdeleteupdate
  3. Data Query Language(DQL 数据查询语言)
    1. 用来查询数据库中表的记录(数据)
    2. 关键字:selectwhere
  4. Data Control Language(DCL 数据控制语言)
    1. 用来定义数据库的访问权限和安全级别,及创建用户
    2. 关键字:GRANTREVOKE

3. 基本的SELECT语句

1) SELECT … FROM

  • 语法
SELECT 标识选择哪些列
FROM 标识从哪个表中选择
  • 选择全部列
SELECT *
FROM departments;
  • 选择特定的列:
SELECT department_id, location_id
FROM departments;

2) 列的别名

  • 重命名一个列
  • 便于计算
  • 紧跟列名,也可以在列名和别名之间加入关键字AS,别名使用双引号,以便在别名中包含空格或特殊的字符并区分大小写
  • AS 可以省略
  • 建议别名简短,见名知意
  • 举例:
SELECT last_name AS name, commission_pct comm
FROM employees;

3) 去除重复行

DISTINCT关键字

SELECT DISTINCT department_id FROM employees;

4) 空值参与运算

空值:null ( 不等同于0、’ ‘、 ’null‘ )

实际问题的解决方案:引入IFNULL

SELECT employee_id, salary "月工资", salary * (1 + IFNULL(commission_pct, 0)) * 12 "年工资" FROM employees;

在MySQL 里面,空值不等于空字符串。一个空字符串的长度是0,而一个空值的长度是空。而且,空值是占用空间的

5) 着重号 ``

必须保证字段没有和保留字、数据库系统或常见方法冲突

如果坚持使用,在SQL语句中使用 ` ` 引起来

SELECT * FROM `order`;

6) 查询常数

SELECT '小张科技' as "公司名", employee_id, last_name FROM employees;

4. 显示表结构

显示表中字段的详细信息

DESCRIBE employees;
或
DESC employees;
mysql> desc employees;
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| employee_id | int(6) | NO | PRI | 0 | |
| first_name | varchar(20) | YES | | NULL | |
| last_name | varchar(25) | NO | | NULL | |
| email | varchar(25) | NO | UNI | NULL | |
| phone_number | varchar(20) | YES | | NULL | |
| hire_date | date | NO | | NULL | |
| job_id | varchar(10) | NO | MUL | NULL | |
| salary | double(8,2) | YES | | NULL | |
| commission_pct | double(2,2) | YES | | NULL | |
| manager_id | int(6) | YES | MUL | NULL | |
| department_id | int(4) | YES | MUL | NULL | |
+----------------+-------------+------+-----+---------+-------+
11 rows in set (0.00 sec)

其中,各个字段的含义:

  • Field:表示字段名称
  • Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的
  • Null:表示该列是否可以存储NULL值
  • Key:表示该列是否已编制索引
  • PRI表示该列是表主键的一部分
  • UNI表示该列是UNIQUE索引的一 部分
  • MUL表示在列中某个给定值允许出现多次
  • Default:表示该列是否有默认值,如果有,那么值是多少
  • Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等

5. 过滤数据

  • 语法:
SELECT 字段1,字段2
FROM 表名
WHERE 过滤条件

使用WHERE 子句,将不满足条件的行过滤掉。WHERE子句紧随 FROM子句。

  • 举例:
SELECT employee_id, last_name, job_id, department_id
FROM employees
WHERE department_id = 90;

二、运算符

DUAL 伪表

1. 算术运算符

SELECT 100 + 0, 100 + 50 * 30, 100 - 35.5 FROM DUAL;
  • 一个整数类型的值对整数进行加法和减法操作,结果还是一个整数
  • 一个整数类型的值对浮点数进行加法和减法操作,结果是一个浮点数
  • 在Java中, + 的左右两边如果有字符串,那么表示字符串的拼接。但是在MySQL中 + 只表示数值相加。如果遇到非数值类型,先尝试转成数值,如果转失败,就按0计算。(注:MySQL 中字符串拼接要使用字符串函数CONCAT()实现)
  • 在数学运算中,0不能用作除数,在MySQL中,一个数除以0为NULL

2. 比较运算符

1) 等号运算符

比较运算符用来对表达式左边的操作数和右边的操作数进行比较,比较的结果为真则返回1,比较的结果为假则返回0,其他情况则返回NULL

比较运算符经常被用来作为SELECT查询语句的条件来使用,返回符合条件的结果记录

如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL

mysql> SELECT 1 = 1, 1 = '1', 1 = 0, 'a' = 'a', (5 + 3) = (2 + 6), '' = NULL , NULL = NULL;
+-------+---------+-------+-----------+-------------------+-----------+-------------+
| 1 = 1 | 1 = '1' | 1 = 0 | 'a' = 'a' | (5 + 3) = (2 + 6) | '' = NULL | NULL = NULL |
+-------+---------+-------+-----------+-------------------+-----------+-------------+
|   1   |    1    |   0   |     1     |          1        |    NULL   |     NULL    |
+-------+---------+-------+-----------+-------------------+-----------+-------------+
1 row in set (0.00 sec)
mysql> SELECT 1 = 2, 0 = 'abc', 1 = 'abc' FROM DUAL;
+-------+-----------+-----------+
| 1 = 2 | 0 = 'abc' | 1 = 'abc' |
+-------+-----------+-----------+
|   0   |     1     |     0     |
+-------+-----------+-----------+
1 row in set, 2 warnings (0.00 sec)
  • 如果等号两边的值、字符串或表达式都为字符串,则MySQL会按照字符串进行比较,其比较的是每个字符串中字符的ANSI编码是否相等
  • 如果等号两边的值都是整数,则MySQL会按照整数来比较两个值的大小
  • 如果等号两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较
  • 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL

2) 不等于运算符

不等于运算符(<>和!=)用于判断两边的数字、字符串或者表达式的值是否不相等, 如果不相等则返回1,相等则返回0。不等于运算符不能判断NULL值。如果两边的值有任意一个为NULL, 或两边都为NULL,则结果为NULL。 SQL语句示例如下:

mysql> SELECT 1 <> 1, 1 != 2, 'a' != 'b', (3+4) <> (2+6), 'a' != NULL, NULL <> NULL;
+--------+--------+------------+----------------+-------------+--------------+
| 1 <> 1 | 1 != 2 | 'a' != 'b' | (3+4) <> (2+6) | 'a' != NULL | NULL <> NULL |
+--------+--------+------------+----------------+-------------+--------------+
|    0   |    1   |      1     |        1       |     NULL    |      NULL    |
+--------+--------+------------+----------------+-------------+--------------+
1 row in set (0.00 sec)

此外,还有非符号类型的运算符:

运算符 名称 含义 举例
IS NOT NULL 不为空运算符 判断值、字符串或表达式是否不为空 select b from table where a is not null
LEAST 最小值运算符 在多个值中返回最小值 select d from table where c least(a,b)
GREATEST 最大值运算符 在多个值中返回最大值 select d from table where c greatest(a,b)
BETWEEN AND 两值之间的运算符 判断一个值是否在两个值之间 select d from table where c between a and b
ISNULL 为空运算符 判断一个值、字符串或表达式是否为空 select b from table where a isnull
IN 属于运算符 判断一个值是否为列表中的任意一个值 select d from table where c in(a,b)
NOT IN 不属于运算符 判断一个值是否不是列表中的任意一个值 select d from table where c not in(a,b)
LIKE 模糊匹配运算符 判断一个值是否符合模糊匹配规则 select c from table where a like b
REGEXP 正则表达式运算符 判断一个值是否符合正则表达式的规则 select c from table where a regexp b
RLIKE 正则表达式运算符 判断一个值是否符合正则表达式的规则 select c from table where a rlike b

3) 空运算符

空运算符 (IS NULL 或者 ISNULL) 判断一个值是否为NULL,如果为NULL则返回1,否则返回0

mysql> SELECT NULL IS NULL, ISNULL(NULL), ISNULL('a'), 1 IS NULL;
+--------------+--------------+-------------+-----------+
| NULL IS NULL | ISNULL(NULL) | ISNULL('a') | 1 IS NULL |
+--------------+--------------+-------------+-----------+
|      1       |       1      |      0      |     0     |
+--------------+--------------+-------------+-----------+
1 row in set (0.00 sec)

4) 非空运算符

非空运算符(IS NOT NULL)判断一个值是否不为NULL,如果不为NULL则返回1,否则返回0

5) 最小值运算符

语法格式为:LEAST(值1,值2,…,值n)。其中,“值n”表示参数列表中有n个值。在有 两个或多个参数的情况下,返回最小值

mysql> SELECT LEAST (1,0,2), LEAST('b','a','c'), LEAST(1,NULL,2);
+---------------+--------------------+-----------------+
| LEAST (1,0,2) | LEAST('b','a','c') | LEAST(1,NULL,2) |
+---------------+--------------------+-----------------+
|       0       |          a         |        NULL     |
+---------------+--------------------+-----------------+
1 row in set (0.00 sec)

当参数是整数或者浮点数时,LEAST将返回其中最小的值;当参数为字符串时,返回字母表中顺序最靠前的字符;当比较值列表中有NULL时,不能判断大小,返回值为NULL

6) 最大值运算符

语法格式为:GREATEST(值1,值2,…,值n)。其中,n表示参数列表中有n个值。当有 两个或多个参数时,返回值为最大值。假如任意一个自变量为NULL,则GREATEST()的返回值为NULL

mysql> SELECT GREATEST(1,0,2), GREATEST('b','a','c'), GREATEST(1,NULL,2);
+-----------------+-----------------------+--------------------+
| GREATEST(1,0,2) | GREATEST('b','a','c') | GREATEST(1,NULL,2) |
+-----------------+-----------------------+--------------------+
|         2       |             c         |         NULL       |
+-----------------+-----------------------+--------------------+
1 row in set (0.00 sec)

当参数中是整数或者浮点数时,GREATEST将返回其中最大的值;当参数为字符串时, 返回字母表中顺序最靠后的字符;当比较值列表中有NULL时,不能判断大小,返回值为NULL

7) BETWEEN AND运算符

BETWEEN运算符使用的格式通常为SELECT D FROM TABLE WHERE C BETWEEN A AND B,此时,当C大于或等于A,并且C小于或等于B时,结果为1,否则结果为0

8) IN运算符

IN运算符用于判断给定的值是否是IN列表中的一个值,如果是则返回1,否则返回0。如果给定的值为NULL,或者IN列表中存在NULL,则结果为NULL

mysql> SELECT 'a' IN ('a','b','c'), 1 IN (2,3), NULL IN ('a','b'), 'a' IN ('a', NULL);
+----------------------+------------+-------------------+--------------------+
| 'a' IN ('a','b','c') | 1 IN (2,3) | NULL IN ('a','b') | 'a' IN ('a', NULL) |
+----------------------+------------+-------------------+--------------------+
|            1         |      0     |         NULL      |          1         |
+----------------------+------------+-------------------+--------------------+

9) NOT IN运算符

NOT IN运算符用于判断给定的值是否不是IN列表中的一个值,如果不是IN列表中的一 个值,则返回1,否则返回0

10) LIKE运算符

LIKE运算符主要用来匹配字符串,通常用于模糊匹配,如果满足条件则返回1,否则返回 0。如果给定的值或者匹配条件为NULL,则返回结果为NULL

“%":匹配0个或多个字符
“_”:只能匹配一个字符

11) REGEXP运算符

REGEXP运算符用来匹配字符串,语法格式为: expr REGEXP 匹配条件

(1)‘^’匹配以该字符后面的字符开头的字符串。

(2)‘$’匹配以该字符前面的字符结尾的字符串。

(3)‘.’匹配任何一个单字符。

(4)“[…]”匹配在方括号内的任何字符。例如,“[abc]”匹配“a”或“b”或“c”。为了命名字符的范围,使用一 个‘-’。“[a-z]”匹配任何字母,而“[0-9]”匹配任何数字。

(5)‘’匹配零个或多个在它前面的字符。例如,“x”匹配任何数量的‘x’字符,“[0-9]”匹配任何数量的数字, 而“”匹配任何数量的任何字符。

3. 逻辑运算符

逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结果为1、0或者NULL

MySQL中支持4种逻辑运算符如下:

运算符 作用 举例
NOT 或 ! 逻辑非 select not a
AND 或 && 逻辑与 select a and bselect a && b
OR 或 || 逻辑或 select a or b 或`select a
XOR 逻辑异或 select a xor b

4. 位运算

位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算, 最后将计算结果从二进制变回十进制数

MySQL支持的位运算符如下:

运算符 作用 举例
& 按位与 select a & b
| 按位或 `select a
^ 按位异或 select a ^ b
~ 按位取反 select ~ a
>> 按位右移 select a >> 2
<< 按位左移 select a << 2

5. 运算符的优先级

数字编号越大,优先级越高,优先级高的运算符先进行计算

优先级 运算符
1 :=、=(赋值)
2 ||、OR、XOR
3 &&、AND
4 NOT
5 BETWEEN、CASE、WHERE、THEN、ELSE
6 =(比较运算符)、<+>、>=、>、<=、<、<>、!=、IS、LIKE、REGEXP、IN
7 |
8 &
9 <<、>>
10 -、+
11 *、/、DIV、%、MOD
12 ^
13 -(负号)、~(按位取反)
14 !
15 ()

6.扩展:使用正则表达式查询

选项 说明 例子 匹配值示例
^ 匹配文本的开始字符 ^b匹配以字母b开头的字符串 book、big
$ 匹配文本的结束字符 st$匹配以st结尾的字符串 test、resist
. 匹配任何单个字符 b.t匹配任何b和t之间有一个字符的字符串 bit、bat
* 匹配零个或多个在他前面的字符 f*n匹配字符n前面有任意个字符f的字符串 fn、fan
+ 匹配前面的字符1次或多次 ba+匹配以b开头后面紧跟至少有一个a的字符串 ba、bay
<字符串> 匹配包含指定的字符串的文本 匹配包含fa的字符串 fan、afa
[字符集合] 匹配字符集和中的任意一个字符 [xz]匹配包含x或z的字符串 dizzy、zebra
[^] 匹配不在括号中的任意字符 [^abc]匹配任何不包含a、b、c的字符串 desk、fox
字符串{n,} 匹配前面的字符串至少n次 b{2}匹配含2个或更多b的字符串 bbb、bbbb
字符串{n,m} 匹配前面的字符串至少n次,至多m次,如果n为0.此参数为可选参数 b{2,4}匹配含最少两个,最多四个b的字符串 bb、bbb、bbbb

三、排序与分页

1. 排序规则

  • 使用 ORDER BY 子句排序,默认按照升序排序

    • ASC(ascend): 升序
    • DESC(descend):降序
  • ORDER BY 子句在SELECT语句的结尾

1) 单列排序

SELECT last_name, job_id, department_id, hire_date
FROM employees
ORDER BY hire_date;

2) 多列排序

  • 可以使用不在SELECT列表中的列排序
  • 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序

2. 分页

  • 格式:
LIMIT [位置偏移量,] 行数
  • 举例:
--前10条记录:
SELECT * FROM 表名 LIMIT 0,10;
或者
SELECT * FROM 表名 LIMIT 10;
--第11至20条记录:
SELECT * FROM 表名 LIMIT 10,10;
--第21至30条记录:
SELECT * FROM 表名 LIMIT 20,10;

MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同

  • 分页显式公式:(当前页数-1)* 每页条数,每页条数
SELECT * FROM table
LIMIT(PageNo - 1) * PageSize, PageSize;
  • 注意:LIMIT 子句必须放在整个SELECT语句的最后

四、多表查询

1. 多表查询分类讲解

1) 自连接

SELECT CONCAT(worker.last_name , ' works for ', manager.last_name)
FROM employees worker, employees manager
WHERE worker.manager_id = manager.employee_id;

2) 内连接与外连接

  • 内连接: 合并多个表中具有相同列的对应行
SELECT emp.employee_id, dep.department_name
FROM employee emp JOIN department dep
ON emp.`department_id` = dep.`department_id`;
  • 外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的 行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)
  • 如果是左外连接,则连接条件中左边的表也称为主表右边的表称**为从表 **

LEFT OUTER JOIN

SELECT last_name, department_name
FROM employees emp LEFT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;
  • 如果是右外连接,则连接条件中右边的表也称为主表左边的表为从表

RIGHT OUTER JOIN

SELECT last_name, department_name
FROM employees emp RIGHT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;

2. UNION的使用

合并查询结果

利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数数据类型必须相同,并且相互对应。各个SELECT语句之间使用UNION或UNION ALL关键字分隔

语法格式:

SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2

UNION操作符

UNION 操作符返回两个查询的结果集的并集,去除重复记录

UNION ALL操作符

UNION ALL操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重

注意:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效率。

举例:查询部门编号>90邮箱包含a的员工信息

#方式1
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
#方式2
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id>90;

举例:查询中国用户中男性的信息以及美国用户中年男性的用户信息

SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';

3.七种SQL JOINS的实现

mysql基础学习笔记_第1张图片

# 中图:内连接
SELECT employee_id,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`;

# 左上图:左外连接
SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`;

# 右上图:右外连接
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;

# 左中图:
SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL;

# 右中图:
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;


# 左下图:满外连接
# 方式1:左上图 UNION ALL 右中图
SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
UNION ALL
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;


# 方式2:左中图 UNION ALL 右上图
SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL
UNION ALL
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;

# 右下图:左中图  UNION ALL 右中图
SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL
UNION ALL
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;

4. SQL99语法的新特性

1) 自然连接

NATURAL JOIN 用来表示自然连接。可以理解为 SQL92 中的等值连接。会自动查询两张连接表中所有相同的字段 ,然后进行等值连接 。

在SQL92标准中:

SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;

在 SQL99 中你可以写成:

SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;

2) USING连接

SQL99支持使用USING指定数据表里的同名字段进行等值连接,但是只能配合JOIN一起使用。比如:

SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);

与自然连接 NATURAL JOIN 不同的是,USING 指定了具体的相同的字段名称,需要在 USING 的括号 () 中填入要指定的同名字段。同时使用JOIN…USING可以简化JOIN ON的等值连接。它与下面的SQL查询结果是相同的:

SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;

5. 小结

表连接的约束条件可以有三种方式:WHERE, ON, USING

  • WHERE:适用于所有关联查询
  • ON :只能和JOIN一起使用,只能写关联条件。虽然关联条件可以并到WHERE中和其他条件一起写,但分开写可读性更好
  • USING:只能和JOIN一起使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字 段值相等

多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让SQL查询性能下降得很严重,因此不要连接不必要的表

在许多 DBMS 中,也都会有最大连接表的限制

# 习题
# 注意:当两个表外连接之后,组成主表和从表,主表的连接字段是不为空的,从表的连接字段可能为空,因此从表的关键字段用来判断是否为空。

# 1.查询哪些部门没有员工
# 方式一
SELECT d.department_id
FROM departments d LEFT JOIN employees e
ON d.`department_id` = e.`department_id`
WHERE e.`department_id` IS NULL;

# 方式二
SELECT department_id
FROM departments d
WHERE NOT EXISTS (
		SELECT *
    	FROM employees e
    	WHERE e.`department_id` = d.`department_id`
);

# 2.查询哪个城市没有部门
SELECT l.location_id, l.city
FROM locations l LEFT JOIN departments d
ON l.`location_id` = d.`location_id`
WHERE d.`location_id` IS NULL;

# 3.查询部门名为 Sales 或 IT 的员工信息
SELECT e.employee_id, e.last_name, e.department_id
FROM employees e JOIN department d
ON e.`department_id` = d.`department_id`
WHERE d.`department_name` IN ('Sales', 'IT');

五、单行函数

1. 数值函数

1) 基本函数

函数 用法
ABS(x) 返回x的绝对值
SIGN(X) 单元格
PI() 返回圆周率的值
CEIL(x),CEILING(x) 返回大于或等于某个值的最小整数
FLOOR(x) 返回小于或等于某个值的最大整数
LEAST(e1,e2,e3…) 返回列表中的最小值
GREATEST(e1,e2,e3…) 返回列表中的最大值
MOD(x,y) 返回X除以Y后的余数
RAND() 返回0~1的随机值
RAND(x) 返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机 数
ROUND(x) 返回一个对x的值进行四舍五入后,最接近于X的整数
ROUND(x,y) 返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位
TRUNCATE(x,y) 返回数字x截断为y位小数的结果
SQRT(x) 返回x的平方根。当X的值为负数时,返回NULL

2) 角度与弧度互换函数

函数 用法
RADIANS(x) 将角度转化为弧度,其中,参数x为角度值
DEGREES(x) 将弧度转化为角度,其中,参数x为弧度值

3) 三角函数

函数 用法
SIN(x) 将角度转化为弧度,其中,参数x为角度值
ASIN(x) 将弧度转化为角度,其中,参数x为弧度值
COS(x) 返回x的余弦值,其中,参数x为弧度值
ACOS(x) 返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL
TAN(x) 返回x的正切值,其中,参数x为弧度值
ATAN(x) 返回x的反正切值,即返回正切值为x的值
ATAN2(m,n) 返回两个参数的反正切值
COT(x) 返回x的余切值,其中,X为弧度值

4) 指数与对数函数

函数 用法
POW(x,y),POWER(X,Y) 返回x的y次方
EXP(X) 返回e的X次方,其中e是一个常数,2.718281828459045
LN(X),LOG(X) 返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG10(X) 返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG2(X) 返回以2为底的X的对数,当X <= 0 时,返回NULL

5) 进制间的转换

函数 用法
BIN(x) 返回x的二进制编码
HEX(x) 返回x的十六进制编码
OCT(x) 返回x的八进制编码
CONV(x,f1,f2) 返回f1进制数变成f2进制数

2. 字符串函数

函数 用法
ASCII(S) 返回字符串S中的第一个字符的ASCII码值
CHAR_LENGTH(s) 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同
LENGTH(s) 返回字符串s的字节数,和字符集有关
CONCAT(s1,s2,…,sn) 连接s1,s2,…,sn为一个字符串
CONCAT_WS(x, s1,s2,…,sn) 同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x
INSERT(str, idx, len, replacestr) 将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr
REPLACE(str, a, b) 用字符串b替换字符串str中所有出现的字符串a
UPPER(s) 或 UCASE(s) 将字符串s的所有字母转成大写字母
LOWER(s) 或LCASE(s) 将字符串s的所有字母转成小写字母
LEFT(str,n) 返回字符串str最左边的n个字符
RIGHT(str,n) 返回字符串str最右边的n个字符
LPAD(str, len, pad) 用字符串pad对str最左边进行填充,直到str的长度为len个字符
RPAD(str ,len, pad) 用字符串pad对str最右边进行填充,直到str的长度为len个字符
LTRIM(s) 去掉字符串s左侧的空格
RTRIM(s) 去掉字符串s右侧的空格
TRIM(s) 去掉字符串s开始与结尾的空格
TRIM(s1 FROM s) 去掉字符串s开始与结尾的s1
TRIM(LEADING s1 FROM s) 去掉字符串s开始处的s1
TRIM(TRAILING s1 FROM s) 去掉字符串s结尾处的s1
REPEAT(str, n) 返回str重复n次的结果
SPACE(n) 返回n个空格
STRCMP(s1,s2) 比较字符串s1,s2的ASCII码值的大小
SUBSTR(s,index,len) 返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、 MID(s,n,len)相同
LOCATE(substr,str) 返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0
ELT(m,s1,s2,…,sn) 返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如果m=n,则返回sn
FIELD(s,s1,s2,…,sn) 返回字符串s在字符串列表中第一次出现的位置
FIND_IN_SET(s1,s2) 返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分隔的字符串
REVERSE(s) 返回s反转后的字符串
NULLIF(value1,value2) 比较两个字符串,如果value1与value2相等,则返回NULL,否则返回 value1

注意:MySQL中,字符串的位置是从1开始的

3. 日期和时间函数

1) 获取日期、时间

函数 用法
CURDATE() ,CURRENT_DATE() 返回当前日期,只包含年、 月、日
CURTIME() , CURRENT_TIME() 返回当前时间,只包含时、 分、秒
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() 返回当前系统日期和时间
UTC_DATE() 返回UTC(世界标准时间) 日期
UTC_TIME() 返回UTC(世界标准时间) 时间

2) 日期与时间戳的转换

函数 用法
UNIX_TIMESTAMP() 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884
UNIX_TIMESTAMP(date) 将时间date以UNIX时间戳的形式返回。
FROM_UNIXTIME(timestamp) 将UNIX时间戳的时间转换为普通格式的时间

3) 获取月份、星期、星期数、天数等函数

函数 用法
YEAR(date) / MONTH(date) / DAY(date) 返回具体的日期值
HOUR(time) / MINUTE(time) / SECOND(time) 返回具体的时间值
FROM_UNIXTIME(timestamp) 将UNIX时间戳的时间转换为普通格式的时间
MONTHNAME(date) 返回月份:January,…
DAYNAME(date) 返回星期几:MONDAY,TUESDAY…SUNDAY
WEEKDAY(date) 返回周几,注意,周1是0,周2是1,。。。周日是6
QUARTER(date) 返回日期对应的季度,范围为1~4
WEEK(date) , WEEKOFYEAR(date) 返回一年中的第几周
DAYOFYEAR(date) 返回日期是一年中的第几天
DAYOFMONTH(date) 返回日期位于所在月份的第几天
DAYOFWEEK(date) 返回周几,注意:周日是1,周一是2,。。。周六是 7

4) 日期的操作函数

函数 用法
EXTRACT(type FROM date) 返回指定日期中特定的部分,type指定返回的值

EXTRACT(type FROM date)函数中type的取值与含义:

TYPE取值 含义
MICROSERCOND 返回毫秒数
SECOND 返回秒数
MINUTE 返回分钟数
HOUR 返回小时数
DAY 返回天数
WEEK 返回日期在一年中的第几个星期
MONTH 返回日期在一年中的第几个月
QUARTER 返回日期在一年中的第几个季度
YEAR 返回日期的年份
SECOND_MINCROSECOND 返回秒和毫秒数
MINUTE_MINCROSECOND 返回分钟和毫秒值
MINUTE_SECOND 返回分钟和秒值
HOUR_MICROSECOND 返回小时和毫秒值
HOUR_SECOND 返回小时和秒值
HOUR_MINUTE 返回小时和分钟值
DAY_MICROSECOND 返回天和毫秒值
DAY_SECOND 返回天和秒值
DAY_MINUTE 返回天和分钟值
DAY_HOUR 返回天和小时值
YEAR_MONTH 返回年和月

5) 时间和秒钟转换的函数

函数 用法
TIME_TO_SEC(time) 将 time 转化为秒并返回结果值。转化的公式为: 小时*3600+分钟 *60+秒
SEC_TO_TIME(seconds) 将 seconds 描述转化为包含小时、分钟和秒的时间

6) 计算日期和时间的函数

函数 用法
DATE_ADD(datetime, INTERVAL expr type), ADDDATE(date,INTERVAL expr type) 返回与给定日期时间相差INTERVAL时间段的日期时间
DATE_SUB(date,INTERVAL expr type), SUBDATE(date,INTERVAL expr type) 返回与date相差INTERVAL时间间隔的日期

上述函数中type的取值:

间隔类型 含义
HOUR 小时
MINUTE 分钟
SECOND
YEAR
MONTH
DAY
YEAR_MONTH 年和月
DAY_HOUR 日和小时
DAY_MINUTE 日和分钟
DAY_SECOND 日和秒
HOUR_MINUTE 小时和发展
HOUR_SECOND 小时和秒
MINUTE_SECOND 分钟和秒
函数 用法
ADDTIME(time1,time2) 返回time1加上time2的时间。当time2为一个数字时,代表的是 秒 ,可以为负数
SUBTIME(time1,time2) 返回time1减去time2后的时间。当time2为一个数字时,代表的 是 秒 ,可以为负数
DATEDIFF(date1,date2) 返回date1 - date2的日期间隔天数
TIMEDIFF(time1, time2) 返回time1 - time2的时间间隔
FROM_DAYS(N) 返回从0000年1月1日起,N天以后的日期
TO_DAYS(date) 返回日期date距离0000年1月1日的天数
LAST_DAY(date) 返回date所在月份的最后一天的日期
MAKEDATE(year,n) 针对给定年份与所在年份中的天数返回一个日期
MAKETIME(hour,minute,second) 将给定的小时、分钟和秒组合成时间并返回
PERIOD_ADD(time,n) 返回time加上n后的时间

7) 日期的格式化与解析

函数 用法
DATE_FORMAT(date,fmt) 按照字符串fmt格式化日期date值
TIME_FORMAT(time,fmt) 按照字符串fmt格式化时间time值
GET_FORMAT(date_type,format_type) 返回日期字符串的显示格式
STR_TO_DATE(str, fmt) 按照字符串fmt对str进行解析,解析为一个日期

上述 非GET_FORMAT 函数中fmt参数常用的格式符:

格式符 说明 格式符 说明
%Y 4位数字表示年份 %y 表示两位数字表示年份
%M 月名表示月份(January,…) %m 两位数字表示月份 (01,02,03。。。)
%b 缩写的月名(Jan.,Feb.,…) %c 数字表示月份(1,2,3,…)
%D 英文后缀表示月中的天数 (1st,2nd,3rd,…) %d 两位数字表示月中的天数(01,02…)
%e 数字形式表示月中的天数 (1,2,3,4,5…)
%H 两位数字表示小数,24小时制 (01,02…) %h 和%I 两位数字表示小时,12小时制 (01,02…)
%k 数字形式的小时,24小时制(1,2,3) %l 数字形式表示小时,12小时制 (1,2,3,4…)
%i 两位数字表示分钟(00,01,02) %S 和%s 两位数字表示秒(00,01,02…)
%W 一周中的星期名称(Sunday…) %a 一周中的星期缩写(Sun., Mon.,Tues.,…)
%w 以数字表示周中的天数 (0=Sunday,1=Monday…)
%j 以3位数字表示年中的天数(001,002…) %U 以数字表示年中的第几周, (1,2,3。。)其中Sunday为周中第一 天
%u 以数字表示年中的第几周, (1,2,3。。)其中Monday为周中第一 天
%T 24小时制 %r 12小时制
%p AM或PM %% 表示%

4. 流程控制函数

流程处理函数可以根据不同的条件,执行不同的处理流程,可以在SQL语句中实现不同的条件选择。 MySQL中的流程处理函数主要包括IF()、IFNULL()和CASE()函数

函数 用法
IF(value,value1,value2) 如果value的值为TRUE,返回value1, 否则返回value2
IFNULL(value1, value2) 如果value1不为NULL,返回value1,否则返回value2
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 … [ELSE resultn] END 相当于Java的if…else if…else…
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1 … [ELSE 值n] END 相当于Java的switch…case…

5. 加密与解密函数

加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。这些函数在保证数据库安全时非常有用

函数 用法
PASSWORD(str) 返回字符串str的加密版本,41位长的字符串。加密结果不可逆 ,常用于用户的密码加密
MD5(str) 返回字符串str的md5加密后的值,也是一种加密方式。若参数为 NULL,则会返回NULL
SHA(str) 从原明文密码str计算并返回加密后的密码字符串,当参数为 NULL时,返回NULL。 SHA加密算法比MD5更加安全 。
ENCODE(value,password_seed) 返回使用password_seed作为加密密码加密value
DECODE(value,password_seed) 返回使用password_seed作为加密密码解密value

6. MySQL信息函数

MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地 对数据库进行维护工作

函数 用法
VERSION() 返回当前MySQL的版本号
CONNECTION_ID() 返回当前MySQL服务器的连接数
DATABASE(),SCHEMA() 返回MySQL命令行当前所在的数据库
USER(),CURRENT_USER()、SYSTEM_USER(), SESSION_USER() 返回当前连接MySQL的用户名,返回结果格式为 “主机名@用户名”
CHARSET(value) 返回字符串value自变量的字符集
COLLATION(value) 返回字符串value的比较规则

MySQL中有些函数无法对其进行具体的分类,但是这些函数在MySQL的开发和运维过程中也是不容忽视的

函数 用法
FORMAT(value,n) 返回对数字value进行格式化后的结果数据。n表示 四舍五入 后保留 到小数点后n位
CONV(value,from,to) 将value的值进行不同进制之间的转换
INET_ATON(ipvalue) 将以点分隔的IP地址转化为一个数字
INET_NTOA(value) 将数字形式的IP地址转化为以点分隔的IP地址
BENCHMARK(n,expr) 将表达式expr重复执行n次。用于测试MySQL处理expr表达式所耗费 的时间
CONVERT(value USING char_code) 将value所使用的字符编码修改为char_code

六、聚合函数

1. 聚合函数介绍

  • 什么是聚合函数

聚合函数作用于一组数据,并对一组数据返回一个值

  • 聚合函数类型
    • AVG()
    • SUM()
    • MAX()
    • MIN()
    • COUNT()

1) AVG和SUM函数

SELECT AVG(salary), SUM(salary)
FROM employees
WHERE job_id LIKE '%REP%';

2) MIN和MAX函数

可以对任意数据类型的数据使用MIN和MAX函数。

SELECT MIN(hire_date), MAX(hire_date)
FROM employees;

3) COUNT函数

COUNT(*)返回表中记录总数,适用于任意数据类型。

SELECT COUNT(*)
FROM employees
WHERE department_id = 50;

COUNT(XXX)返回XXX不为空的记录总数。

SELECT COUNT(commission_pct)
FROM employees
WHERE department_id = 50;

2. GROUP BY

1) 基本使用

可以使用GROUP BY子句将表中的数据分成若干组

SELECT column, group_function(column)
FROM table
[WHERE condition]
[GROUP BY group_by_expression]
[ORDER BY column];

结论1:SELECT中出现的非组函数的字段必须声明在GROUP BY中

​ 反之,GROUP BY中声明的字段可以不出现在SELECT中

结论2:GROUP BY声明在FROM后面、WHERE后面、ORDER BY前面、LIMIT前面

2) 使用WITH ROLLUP

SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;

注意: 当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的

3. HAVING

1) 基本使用

过滤分组:HAVING子句

  1. 行已经被分组
  2. 使用了聚合函数
  3. 满足HAVING 子句中条件的分组将被显示
  4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary)>10000 ;

要求

  • 如果过滤条件中使用了聚合函数,则必须使用HAVING来替换WHERE
  • 当过滤条件中没有聚合函数时,则次过滤条件声明在WHERE中或HAVING中都可以。WHERE的执行效率高。
  • HAVING必须声明在GROUP BY 的后面
  • 开发中,使用HAVING的前提是SQL中使用了GROUP BY

2) WHERE和HAVING的对比

区别1:WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件; HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件

在需要对数据进行分组统计的时候,HAVING 可以完成 WHERE 不能完成的任务,HAVING 在 GROUP BY 之 后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选。另外,WHERE排除的记录不再包括在分组中

区别2:如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接 后筛选。

在关联查询中WHERE比HAVING更高效。因为WHERE可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低

小结如下:

关键字 用法 缺点
WHERE 先筛选数据再关联,执行效率高 不能使用分组中的计算函数进行筛选
HAVING 可以使用分组中的计算函数 在最后的结果集中进行筛选,执行效率较低

4. SELECT的执行过程

1) 查询的结构

#方式1:
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式2:
SELECT ...,....,...
FROM ... JOIN ...
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#其中:
#(1)from:从哪些表中筛选
#(2)on:关联多表查询时,去除笛卡尔积
#(3)where:从表中筛选的条件
#(4)group by:分组依据
#(5)having:在统计结果中再次筛选
#(6)order by:排序
#(7)limit:分页

需要记住 SELECT 查询时的两个顺序:

1. 关键字的顺序是不能颠倒的:

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...

2. SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):

FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT

比如一个 SQL 语句,关键字顺序和执行顺序:

SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5
FROM player JOIN team ON player.team_id = team.team_id # 顺序 1
WHERE height > 1.80 # 顺序 2
GROUP BY player.team_id # 顺序 3
HAVING num > 2 # 顺序 4
ORDER BY num DESC # 顺序 6
LIMIT 2 # 顺序 7

2) SQL的执行原理

SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

  1. 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1
  2. 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2
  3. 添加外部行。如果使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟 表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3
  • 当然如果操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是原始数据
  • 然后进入第三步和第四步,也就是GROUP和HAVING阶段。在这个阶段中,实际上是在虚拟表vt2的基础上进行分组和分组过滤,得到中间的虚拟表vt3和vt4
  • 当完成条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段
  • 首先在SELECT阶段会提取想要的字段,然后在DISTINCT阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1和vt5-2
  • 当提取想要的字段数据之后,就可以按照指定的字段进行排序,也就是ORDER BY阶段,得到虚拟表vt6
  • 最后在vt6的基础上,取出指定行的记录,也就是LIMIT阶段,得到最终的结果,对应的是虚拟表vt7

七、子查询

1. 基本使用

  • 子查询的基本语法结构:

在这里插入图片描述

  • 子查询(内查询)在主查询之前一次执行完成
  • 子查询的结果被主查询(外查询)使用
  • 注意事项
    • 子查询要包含在括号内
    • 将子查询放在比较条件的右侧
    • 单行操作符对应单行子查询,多行操作符对应多行子查询

2. 子查询的分类

分类方式1:

按内查询的结果返回一条还是多条记录,将子查询分为单行子查询、多行子查询

  • 单行子查询

    mysql基础学习笔记_第2张图片

  • 多行子查询

    mysql基础学习笔记_第3张图片

分类方式2:

按内查询是否被执行多次,将子查询划分为相关(或关联)子查询和不相关(或非关联)子查询

不相关子查询:子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行

相关子查询:如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部

3. 单行子查询

1) 单行比较操作符

操作符 含义
= equal to
> greater than
>= greater than or equal to
< less than
<= less than or equal to
<> not equal to

2) 代码示例

  • 返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资
SELECT last_name, job_id, salary
FROM eployees
WHERE job_id = (
	SELECT job_id
	FROM eployees
    WHERE employee_id = 141
)
AND salary > (
	SELECT salary
	FROM eployees
    WHERE employee_id = 143
);
  • 查询与141号或174号员工的manager_id和department_id相同的其他员工的employee_id, manager_id,department_id
# 实现方式一:不成对比较
SELECT employee_id, manager_id, department_id
FROM employees
WHERE manager_id IN
        (SELECT manager_id
        FROM employees
        WHERE employee_id IN (174,141))
AND department_id IN
        (SELECT department_id
        FROM employees
        WHERE employee_id IN (174,141))
AND employee_id NOT IN(174,141);

# 实现方式二:成对比较
SELECT employee_id, manager_id, department_id
FROM employees
WHERE (manager_id, department_id) IN
        (SELECT manager_id, department_id
        FROM employees
        WHERE employee_id IN (141,174))
AND employee_id NOT IN (141,174);
  • 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id, MIN(salary)
FROM employees
GROUP BY department_id
HAVING MIN(salary) >
            (SELECT MIN(salary)
            FROM employees
            WHERE department_id = 50);

3) CASE中的子查询

  • 显式员工的employee_id,last_name和location。其中,若员工department_id与location_id为1800 的department_id相同,则location为’Canada’,其余则为’USA’
SELECT employee_id, last_name,
    (CASE department_id
    WHEN
        (SELECT department_id FROM departments
        WHERE location_id = 1800)
    THEN 'Canada' ELSE 'USA' END) location
FROM employees;

4) 子查询中的空值问题

SELECT last_name, job_id
FROM employees
WHERE job_id =
(SELECT job_id
FROM employees
WHERE last_name = 'Haas');

子查询不返回任何行

5) 非法使用子查询

SELECT employee_id, last_name
FROM employees
WHERE salary =
(SELECT MIN(salary)
FROM employees
GROUP BY department_id);

多行子查询使用单行比较符

4. 多行子查询

  • 也称为集合比较子查询
  • 内查询返回多行
  • 使用多行比较操作符

1) 多行比较操作符

操作符 含义
IN 等于列表中的任意一个
ANY 需要和单行比较操作符一起使用,和子查询返回的某一个值比较
ALL 需要和单行比较操作符一起使用,和子查询返回的所有值比较
SOME 实际上是ANY的别名,作用相同,一般常使用ANY

2) 示例

  • 返回其它job_id中比job_id为‘IT_PROG’部门任一工资低的员工的员工号、姓名、job_id 以及salary
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE job_id <> 'IT_PROG' 
AND salary < ANY(
	SELECT salary
    FROM emplyees
    WHERE job_id = 'IT_PROG'
);
  • 查询平均工资最低的部门id
#方式1:
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) = (
        SELECT MIN(avg_sal)
        FROM (
            SELECT AVG(salary) avg_sal
            FROM employees
            GROUP BY department_id
            ) dept_avg_sal
);
#方式2:
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL (
        SELECT AVG(salary) avg_sal
        FROM employees
        GROUP BY department_id
);

3) 空值问题

SELECT last_name
FROM employees
WHERE employee_id NOT IN (
    SELECT manager_id
    FROM employees
    WHERE manager_id IS NOT NULL
);

5. 相关子查询

如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询

相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询

mysql基础学习笔记_第4张图片

mysql基础学习笔记_第5张图片

说明:子查询中使用主查询中的列

1) 代码示例

  • 查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
# 方式一:使用相关子查询
SELECT last_name, salary, department
FROM employees e1
WHERE salary > (
		SELECT AVG(salary)
    	FROM employees e2
    	WHERE department_id = e1.`department_id`
);
# 方式二:在FROM中声明子查询
SELECT e.last_name, e.salary, e.department_id
FROM employees e, (
    			SELECT department_id, AVG(salary) avg_sal
    			FROM employees
    			GROUP BY department_id) t_dept_avg_salary
WHERE e.department_id = t_dept_avg_salary.department_id
AND e.salary > t_dept_avg_salary.avg_sal;

在ORDER BY 中使用子查询:

  • 查询员工的id,salary,按照department_name 排序
SELECT employee_id, salary
FROM employees e
ORDER BY (
	SELECT department_name
    FROM departments d
    WHERE e.`department_id` = d.`department_id`
);
  • 若employees表中employee_id与job_history表中employee_id相同的数目不小于2,输出这些相同 id的员工的employee_id,last_name和其job_id
SELECT e.employee_id, last_name,e.job_id
FROM employees e
WHERE 2 <= (SELECT COUNT(*)
        FROM job_history
        WHERE employee_id = e.employee_id
);

2) EXISTS 与 NOT EXISTS 关键字

  • 关联子查询通常也会和 EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行
  • 如果在子查询中不存在满足条件的行:
    • 条件返回 FALSE
    • 继续在子查询中查找
  • 如果在子查询中存在满足条件的行:
    • 不在子查询中继续查找
    • 条件返回 TRUE
  • NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE

查询公司管理者的employee_id,last_name,job_id,department_id信息

# 方式一:EXISTS
SELECT employee_id, last_name, job_id, department_id
FROM employees e1
WHERE EXISTS ( SELECT *
        FROM employees e2
        WHERE e2.manager_id =
        e1.employee_id
);

# 方式二:自连接
SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id
FROM employees e1 JOIN employees e2
ON e1.employee_id = e2.manager_id;

# 方式三:IN
SELECT employee_id, last_name, job_id, department_id
WHERE employee_id IN (
        SELECT DISTINCT manager_id
        FROM employees
);

查询departments表中,不存在于employees表中的部门的department_id和department_name

# 方式一:
SELECT d.department_id, d.department_name
FROM departments e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;

# 方式二:
SELECT department_id, department_name
FROM departments d
WHERE NOT EXISTS (
	SELECT *
    FROM employees e
    WHERE d.`department_id` = e.`department_id`
);

3) 相关更新

UPDATE table1 alias1
SET column = (SELECT expression
FROM table2 alias2
WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据更新另一个表的数据

在employees中增加一个department_name字段,数据为员工对应的部门名称

# 1)
ALTER TABLE employees
ADD(department_name VARCHAR2(14));

# 2)
UPDATE employees e
SET department_name = (SELECT department_name
FROM departments d
WHERE e.department_id = d.department_id);

4) 相关删除

DELETE FROM table1 alias1
WHERE column operator (SELECT expression
FROM table2 alias2
WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据删除另一个表的数据

删除表employees中,其与emp_history表皆有的数据

DELETE FROM employees e
WHERE employee_id in(
    SELECT employee_id
    FROM emp_history
    WHERE employee_id = e.employee_id
);

6. 思考题

求工资比Able的高的员工

#方式1:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e1.last_name = 'Able'
AND e1.`salary` < e2.`salary`;
#方式2:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (
    SELECT salary
    FROM employees
    WHERE last_name = 'Able'
);

7. 练习

  1. 查询和Zlotkey相同部门的员工姓名和工资
SELECT last_name, salary
FROM employees
WHERE department_id = (
	SELECT department_id
    FROM employees
    WHERE last_name = 'Zlotkey'
);
  1. 查询工资比公司平均工资高的员工的员工号,姓名和工资
SELECT employee_id, last_name, salary
FROM employees
WHERE salary > (
	SELECT AVG(salary)
    FROM employee
);
  1. 选择工资大于所有JOB_ID = ‘SA_MAN’ 的员工的工资的员工的last_name, job_id, salary
SELECT last_name, job_id, salary
FROM employees
WHERE salary > ALL (
	SELECT salary
    FROM employees
    WHERE job_id = 'SA_MAN'
);
  1. 查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
SELECT employee_id, last_name
FROM eployees
WHERE department_id IN (
    SELECT DISTINCT department_id
    FROM employees
    WHERE last_name LIKE '%u%'
);
  1. 查询在部门的location_id为1700的部门工作的员工的员工号
SELECT employee_id
FROM employees
WHERE department_id IN (
	SELECT department_id
    FROM departments
    WHERE location_id = 1700
);
  1. 查询管理者是King的员工姓名和工资
SELECT last_name, salary
FROM employees
WHERE manage_id IN (
	SELECT employee_id
    FROM employees
    WHERE last_name = 'King'
);
  1. 查询工资最低的员工信息 (last_name, salary)
SELECT last_name, salary
FROM employees
WHERE salary = (
	SELECT MIN(salary)
    FROM employees
);
  1. 查询平均工资最低的部门信息
# 方式一
SELECT *
FROM departments
WHERE department_id = (
	SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING AVG(salary) = (
    	SELECT MIN(avg_sal)
        FROM (
        	SELECT AVG(salary) avg_sal
            FROM employees
            GROUP BY department_id
        ) t_dept_avg_sal
    )
);

# 方式二
SELECT *
FROM departments
WHERE department_id = (
	SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING AVG(salary) <= ALL (
        SELECT AVG(salary) avg_sal
        FROM employees
        GROUP BY department_id
    )
);

# 方式三: LIMIT
SELECT *
FROM departments
WHERE department_id IN (
    SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING AVG(salary) = (
    	SELECT AVG(salary) avg_sal
        FROM employees
        GROUP BY department_id
        ORDER BY avg_sal ASC
        LIMIT 1
    )
);

# 方式四
SELECT d.*
FROM departments d, (
	SELECT department_id, AVG(salary) avg_sal
    FROM employees
    GROUP BY department_id
    ORDER BY avg_sal ASC
    LIMIT 0,1
) t_dept_avg_sal
WHERE d.`department_id` = t_dept_avg_sal.`department_id`;
  1. 查询平均工资最低的部门信息和该部门的平均工资 (相关子查询)
SELECT d.*, (SELECT AVG(salary) FROM employees WHERE department_id = d.`department_id`) avg_sal
FROM departments d, (
	SELECT department_id, AVG(salary) avg_sal
    FROM employees
    GROUP BY department_id
    ORDER BY avg_sal ASC
    LIMIT 0,1
) t_dept_avg_sal
WHERE d.`department_id` = t_dept_avg_sal.`department_id`;
  1. 查询平均工资最高的job信息
SELECT *
FROM jobs
WHERE job_id = (
	SELECT job_id
    FROM employees
    GROUP BY job_id
    HAVING AVG(salary) = (
    	SELECT MAX(avg_sal)
        FROM (
        	SELECT AVG(salary) avg_sal
            FROM employees
            GROUP BY job_id
        ) t_job_avg_sal
    )
);
  1. 查询平均工资高于公司平均工资的部门有哪些?
SELECT depatment_id
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING AVG(salary) > (
	SELECT AVG(salary)
    FROM eployees
);
  1. 查询出公司中所有manager的详细信息
# 方式1:自连接
SELECT DISTINCT *
FROM employees emp, employees manager
WHERE emp.`manager_id` = manager.`employee_id`;

SELECT DISTINCT *
FROM employees emp JOIN employees manager
ON emp.`manager_id` = manager.`employee_id`; 

# 方式2:子查询
SELECT *
FROM employees
WHERE employee_id IN (
	SELECT manager_id
    FROM employees
);

# 方式3:EXISTS
SELECT *
FROM employees manager
WHERE EXISTS (
	SELECT *
    FROM employees emp
    WHERE manager.`employee_id` = emp.`manager_id`
);
  1. 求各个部门中,最高工资中最低的那个部门的最低工资
# 方式一:
SELECT MIN(salary)
FROM employees
WHERE department_id = (
    SELECT department_id
    FROM employees
	GROUP BY department_id
	HAVING MAX(salary) = (
    	SELECT MIN(max_sal)
        FROM (
        	SELECT MAX(salary) max_sal
            FROM employees
            GROUP BY department_id
        ) t_dept_max_sal
    ) 
);

# 方式二:
SELECT MIN(salary)
FROM employees
WHERE department_id = (
    SELECT department_id
    FROM employees
	GROUP BY department_id
	HAVING MAX(salary) <= ALL (
        SELECT MAX(salary)
        FROM employees
        GROUP BY department_id
    ) 
);

# 方式三:
SELECT MIN(salary)
FROM employees
WHERE department_id = (
    SELECT department_id
    FROM employees
	GROUP BY department_id
	HAVING MAX(salary) = (
        SELECT MAX(salary) max_sal
        FROM employees
        GROUP BY department_id
        ORDER BY max_sal ASC
        LIMIT 0,1
    ) 
);

# 方式四:
FROM employees e, (
	SELECT department_id, MAX(salary) max_sal
    FROM employees
    GROUP BY department_id
    ORDER BY max_sal ASC
    LIMIT 0,1
) t_dept_max_sal
WHERE e.`department_id` = t_dept_max_sal.`department_id`;
  1. 查询平均工资最高的部门的manager的详细信息:last_name, department_id, email, salary
SELECT last_name, department_id, email, salary
FROM employees
WHERE employee_id IN (
	SELECT DISTINCT manager_id
    FROM employees
    WHERE department_id = (
    	SELECT department_id
        FROM employees
        GROUP BY department_id
        HAVING AVG(salary) = (
        	SELECT MAX(avg_sal)
            FROM (
            	SELECT AVG(salary) avg_sal
                FROM employees
                GROUP BY department_id
            ) t_dept_avg_sal
        )
    )
);

SELECT last_name, department_id, email, salary
FROM employees
WHERE employee_id IN (
    SELECT DISTINCT manager_id
    FROM employees e, (
        SELECT department_id, AVG(salary) avg_sal
        FROM employees
        GROUP BY department_id
        ORDER BY avg_sal DESC
        LIMIT 0,1
    ) t_dept_avg_sal
    WHERE e.`department_id` = t_dept_avg_sal.`department_id`
);
  1. 查询部门的部门号,其中不包括job_id是"ST_CLERK"的部门号
SELECT department_id
FROM departments
WHERE department_id NOT IN (
	SELECT DISTINCT department_id
    FROM employees
    WHERE job_id = `ST_CLERK`
);

SELECT department_id
FROM department d
WHERE NOT EXISTS (
	SELECT *
    FROM employees e
    WHERE d.`department_id` = e.`department_id`
    AND e.`job_id` = 'ST_CLERK'
);
  1. 选择所有没有管理者的员工的last_name
SELECT last_name
FROM employees emp
WHERE NOT EXISTS (
	SELECT *
    FROM employees mgr
    WHERE emp.`manager_id` = mgr.`employee_id`
);
  1. 查询员工号、姓名、雇用时间、工资,其中员工的管理者为 ‘De Haan’
SELECT employee_id, last_name, hire_date, salary
FROM employee
WHERE manager_id IN (
	SELECT manager_id
    FROM employee
    WHERE last_name = 'De Haan'
);
  1. 查询各部门中工资比本部门平均工资高的员工的员工号,姓名和工资(相关子查询)
SELECT department_id, last_name, salary
FROM employees e1
WHERE salary > (
	SELECT AVG(salary)
    FROM employees e2
    WHERE e2.`department_id` = e1.`department_id`
);

SELECT e.last_name, e.salary, e.department_id
FROM employees e, (
	SELECT department_id, AVG(salary) avg_sal
    FROM employees
    GROUP BY department_id
) t_dept_avg_sal
WHERE e.`department_id` = t_dept_avg_sal.`department_id`
AND e.`salary` > t_dept_avg_sal.`avg_sal`;
  1. 查询每个部门下的部门人数大于5的部门名称(相关子查询)
SELECT department_name
FROM departments d
WHERE 5 < (
	SELECT COUNT(*)
    FROM employees e
    WHERE d.`department_id` = e.`department_id`
);
  1. 查询每个国家下的部门个数大于2的国家编号(相关子查询)
SELECT country_id
FROM locations l
WHERE 2 < (
	SELECT COUNT(*)
    FROM department d
    WHERE l.`location_id` = d.`location_id`
);

八、创建和管理表

1. 基础知识

1) 标识符命名规则

  • 数据库名、表名不得超过30个字符,变量名限制为29个
  • 必须只能包含 A–Z, a–z, 0–9, _共63个字符
  • 数据库名、表名、字段名等对象名中间不要包含空格
  • 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
  • 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使 用`(着重号)引起来
  • 保持字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据 类型在一个表里是整数,那在另一个表里可就别变成字符型了

2) MySQL中的数据类型

类型 数据变量
整数类型 TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT
浮点类型 FLOAT、DOUBLE
定点数类型 DECIMAL
位类型 BIT
日期时间类型 YEAR、TIME、DATE、DATETIME、TIMESTAMP
文本字符串类型 CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
枚举类型 ENUM
集合类型 SET
二进制字符串类型 BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB
JSON类型 JSON对象、JSON数组
空间数据类型 单值:GEOMETRY、POINT、LINESTRING、POLYGON; 集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、 GEOMETRYCOLLECTION

其中,常用的几类类型介绍如下:

数据类型 描述
INT 从-231到231-1的整型数据。存储大小为 4个字节
CHAR(size) FLOAT、DOUBLE
VARCHAR(size) DECIMAL
FLOAT(M,D) BIT
DOUBLE(M,D) YEAR、TIME、DATE、DATETIME、TIMESTAMP
DECIMAL(M,D) CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
DATE ENUM
BLOB SET
TEXT BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB

2. 创建和管理数据库

1) 创建数据库

  • 方式1:创建数据库

    CREATE DATABASE 数据库名;
    
  • 方式2:创建数据库并指定字符集

    CREATE DATABASE 数据库名 CHARACTER SET 字符集;
    
  • 方式3:判断数据库是否已经存在,不存在则创建数据库( 推荐 )

    CREATE DATABASE IF NOT EXISTS 数据库名;
    

2) 使用数据库

  • 查看当前所有的数据库

    SHOW DATABASES; #有一个S,代表多个数据库
    
  • 查看当前正在使用的数据库

    SELECT DATABASE(); #使用的一个 mysql 中的全局函数
    
  • 查看指定库下所有的表

    SHOW TABLES FROM 数据库名
    
  • 查看数据库的创建信息

    SHOW CREATE DATABASE 数据库名;
    或者:
    SHOW CREATE DATABASE 数据库名\G
    
  • 使用/切换数据库

    USE 数据库名;
    

3) 修改数据库

  • 更改数据库字符集

    ALTER DATABASE 数据库名 CHARACTER SET 字符集; #比如:gbk、utf8等
    
  • 方式1:删除指定的数据库

    DROP DATABASE 数据库名;
    
  • 方式2:删除指定的数据库( 推荐 )

    DROP DATABASE IF EXISTS 数据库名;
    

3. 创建表

1) 创建方式1

  • 语法格式:
CREATE TABLE [IF NOT EXISTS] 表名(
字段1, 数据类型 [约束条件] [默认值],
字段2, 数据类型 [约束条件] [默认值],
字段3, 数据类型 [约束条件] [默认值],
……
[表约束条件]
);

加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表; 如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表

2) 创建方式2

  • 使用 AS subquery 选项,将创建表和插入数据结合起来
CREATE TABLE 表名
	[(column, column, ...)]
AS subquery;
  • 指定的列和子查询中的列要一一对应
  • 通过列名和默认值定义列
CREATE TABLE dept80
AS
SELECT employee_id, last_name, salary*12 ANNSAL, hire_date
FROM employees
WHERE department_id = 80;

3) 查看数据表结构

语法格式如下:

DESC TABLE 表名;
SHOW CREATE TABLE 表名;

使用SHOW CREATE TABLE语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码

4. 修改表

修改表指的是修改数据库中已经存在的数据表的结构

使用 ALTER TABLE 语句可以实现:

  • 向已有的表中添加列
  • 修改现有表中的列
  • 删除现有表中的列
  • 重命名现有表中的列

1) 追加一个列

  • 语法格式如下:
ALTER TABLE 表名 ADD 【COLUMN】 字段名 字段类型 【FIRST|AFTER 字段名】;
  • 举例:
ALTER TABLE dept80
ADD job_id varchar(15);

2) 修改一个列

  • 可以修改列的数据类型,长度、默认值和位置
  • 语法格式如下:
ALTER TABLE 表名 MODIFY 【COLUMN】 字段名1 字段类型 【DEFAULT 默认值】【FIRST|AFTER 字段名2】;
  • 举例:
ALTER TABLE dept80
MODIFY salary double(9,2) default 1000;

3) 重命名一个列

  • 使用 CHANGE old_column new_column dataType子句重命名列
  • 语法格式如下:
ALTER TABLE 表名 CHANGE 【column】 列名 新列名 新数据类型;
  • 举例:
ALTER TABLE dept80
CHANGE department_name dept_name varchar(15);

4) 删除一个列

  • 删除表中某个字段的语法格式如下:
ALTER TABLE 表名 DROP 【COLUMN】字段名

5) 更改表名

  • 方式一:使用RENAME
RENAME TABLE emp
TO myemp;
  • 方式二:
ALTER table dept
RENAME [TO] detail_dept; -- [TO]可以省略

5. 删除表

  • 在MySQL中,当一张数据表没有与其他任何数据表形成关联关系时,可以将当前数据表直接删除
  • 数据和结构都被删除
  • 所有正在运行的相关事务被提交
  • 所有相关索引被删除
  • 语法格式:
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n];
  • IF EXISTS 的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作

  • 举例:

DROP TABLE dept80;
  • 注意:DROP TABLE 语句不能回滚

6. 清空表

  • TRUNCATE TABLE语句:
    • 删除表中所有的数据
    • 释放表的存储空间
  • 举例:
TRUNCATE TABLE detail_dept;
  • TRUNCATE语句不能回滚,而使用 DELETE 语句删除数据,可以回滚

九、数据处理之增删改

1. 插入数据

1) 方式1:VALUES的方式添加

使用这种语法一次只能向表中插入一条数据

情况1:为表的所有字段按默认顺序插入数据

INSERT INTO 表名
VALUES (value1,value2,....);

值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同

举例:

INSERT INTO departments
VALUES (70, 'Pub', 100, 1700);

情况2: 指定字段名插入数据

为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值

举例:

INSERT INTO departments(department_id, department_name)
VALUES (80, 'IT');

情况3:同时插入多条记录

INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开,基本语法格式如下:

INSERT INTO table_name
VALUES
(value1 [,value2, …, valuen]),
(value1 [,value2, …, valuen]),
……
(value1 [,value2, …, valuen]);

或者

INSERT INTO table_name(column1 [, column2, …, columnn])
VALUES
(value1 [,value2, …, valuen]),
(value1 [,value2, …, valuen]),
……
(value1 [,value2, …, valuen]);

2) 方式2:将查询结果插入到表中

INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行

INSET INTO 目标表名
(tar_column1 [, tar_column2, ..., tar_columnn])
SELECT
(src_column1 [, src_column2, …, src_columnn])
FROM 源表名
[WHERE condition]
  • 在 INSERT 语句中加入子查询
  • 子查询中的值列表应与 INSERT 子句中的列名对应
INSERT INTO emp2
SELECT *
FROM employees
WHERE department_id = 90;
INSERT INTO sales_reps(id, name, salary, commission_pct)
SELECT employee_id, last_name, salary, commission_pct
FROM employees
WHERE job_id LIKE '%REP%';

2. 更新数据

  • 使用 UPDATE 语句更新数据
  • 语法如下:
UPDATE table_name
SET column1=value1, column2=value2, ..., column=valuen
[WHERE condition]
  • 可以一次更新多条数据

  • 如果需要回滚数据,需要保证在DML前,进行设置:SET AUTOCOMMIT = FALSE;

  • 使用 WHERE 子句指定需要更新的数据

UPDATE employees
SET department_id = 70
WHERE employee_id = 113;
  • 如果省略 WHERE 子句,则表中的所有数据都将被更新

3. 删除数据

DELETE FROM table_name [WHERE ];

如果没有WHERE子句, DELETE语句将删除表中的所有记录

4. MySQL8新特性:计算列

简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为1、b列值为2,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的

在MySQL 8.0中,CREATE TABLE 和 ALTER TABLE 中都支持增加计算列。下面以CREATE TABLE为例进行讲解

举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的 值

CREATE TABLE tb1(
id INT,
a INT,
b INT,
c INT GENERATED ALWAYS AS (a + b) VIRTUAL
);

十、MySQL数据类型

1. MySQL中的数据类型

类型 举例
整数类型 TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT
浮点类型 FLOAT、DOUBLE
定点数类型 DECIMAL
位类型 BIT
日期时间类型 YEAR、TIME、DATE、DATETIME、TIMESTAMP
文本字符串类型 CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
枚举类型 ENUM
集合类型 SET
二进制字符串类型 BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB
JSON类型 JSON对象、JSON数组
空间数据类型 单值类型:GEOMETRY、POINT、LINESTRING、POLYGON; 集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、 GEOMETRYCOLLECTION

常见数据类型的属性,如下:

MySQL关键字 含义
NULL TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT
NOT NULL FLOAT、DOUBLE
DEFAULT DECIMAL
PRIMARY KEY BIT
AUTO_INCREMENT YEAR、TIME、DATE、DATETIME、TIMESTAMP
UNSIGNED CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
CHARACTER SET name ENUM

2. 整数类型

1) 类型介绍

整数类型一共有 5 种,包括 TINYINT、SMALLINT、MEDIUMINT、INT(INTEGER)和 BIGINT。

它们的区别如下表所示:

整数类型 字节 有符号数取值范围 无符号数取值范围
TINYINT 1 -128~127 0~255
SMALLINT 2 -32768~32767 0~65535
MEDIUMINT 3 -8388608~8388607 0~16777215
INT、INTEGER 4 -2147483648~2147483647 0~4294967295
BIGINT 8 -9223372036854775808~9223372036854775807 0~18446744073709551615

2) 可选属性

  • UNSIGNED

UNSIGNED : 无符号类型(非负),所有的整数类型都有一个可选的属性UNSIGNED(无符号属性),无 符号整数类型的最小取值为0。所以,如果需要在MySQL数据库中保存非负整数值时,可以将整数类型设 置为无符号类型。 int类型默认显示宽度为int(11),无符号int类型默认显示宽度为int(10)

  • ZEROFILL

ZEROFILL : 0填充,(如果某列是ZEROFILL,那么MySQL会自动为当前列添加UNSIGNED属性),如果指 定了ZEROFILL只是表示不够M位时,用0在左边填充,如果超过M位,只要不超过数据存储范围即可

3) 适用场景

TINYINT :一般用于枚举数据,比如系统设定取值范围很小且固定的场景

SMALLINT :可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等

MEDIUMINT :用于较大整数的计算,比如车站每日的客流量等

INT、INTEGER :取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号

BIGINT :只有当处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等

3. 浮点类型

1) 类型介绍

浮点数和定点数类型的特点是可以处理小数 。 MySQL支持的浮点数类型,分别是 FLOAT、DOUBLE、REAL

  • FLOAT 表示单精度浮点数

  • DOUBLE 表示双精度浮点数

  • REAL默认就是 DOUBLE。可以通过以下 SQL 语句实现:

    SET sql_mode = “REAL_AS_FLOAT”;
    

4. 日期与时间类型

MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR类型、TIME类型、DATE类型、DATETIME类型和TIMESTAMP类型

  • YEAR 类型通常用来表示年
  • DATE 类型通常用来表示年、月、日
  • TIME 类型通常用来表示时、分、秒
  • DATETIME 类型通常用来表示年、月、日、时、分、秒
  • TIMESTAMP 类型通常用来表示带时区的年、月、日、时、分、秒
类型 名称 字节 日期格式 最小值 最大值
YEAR 1 YYYY或YY 1901 2155
TIME 时间 3 HH:MM:SS -838:59:59 838:59:59
DATE 日期 3 YYYY-MM-DD 1000-01-01 9999-12-03
DATETIME 日期时间 8 YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00 9999-12-31 23:59:59
TIMESTAMP 日期时间 4 YYYY-MM-DD HH:MM:SS 1970-01-01 00:00:00 UTC 2038-01-19 03:14:07UTC

7. 文本字符串类型

MySQL中,文本字符串总体上分为 CHAR 、 VARCHAR 、 TINYTEXT 、 TEXT 、 MEDIUMTEXT 、 LONGTEXT 、 ENUM 、 SET等类型

8. ENUM类型

ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM 类型只允许从成员中选取单个值,不能一次选取多个值。 其所需要的存储空间由定义ENUM类型时指定的成员个数决定

文本字符串类型 长度 长度范围 占用的存储空间
ENUM L 1 <= L <= 65535 1或2个字节
  • 当ENUM类型包含1~255个成员时,需要1个字节的存储空间
  • 当ENUM类型包含256~65535个成员时,需要2个字节的存储空间
  • ENUM类型的成员个数的上限为65535个

9. SET类型

当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的,具体如下:

成员个数范围(L表示实际成员个数) 占用的存储空间
1 <= L <= 8 1个字节
9 <= L <= 16 2个字节
17 <= L <= 24 3个字节
25 <= L <= 32 4个字节
33 <= L <= 64 8个字节

SET类型在存储数据时成员个数越多,其占用的存储空间越大

注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同

十一、约束

1. 约束的分类

  • 根据约束数据列的限制,约束可分为:
    • 单列约束:每个约束只约束一列
    • 多列约束:每个约束可约束多列数据
  • 根据约束的作用范围,约束可分为:
    • 列级约束:只能作用在一个列上,跟在列的定义后面
    • 表级约束:可以作用在多个列上,不与列一起,而是单独定义
  • 根据约束起的作用,约束可分为:
    • NOT NULL 非空约束,规定某个字段不能为空
    • UNIQUE 唯一约束,规定某个字段在整个表中是唯一的
    • PRIMARY KEY 主键(非空且唯一)约束
    • FOREIGN KEY 外键约束
    • CHECK 检查约束
    • DEFAULT 默认值约束

注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果

  • 添加/ 删除约束时机

    • CREATE TABLE时添加约束

    • ALTER TABLE时增加约束、删除约束

  • 查看某个表已有的约束

#information_schema数据库名(系统库)
#table_constraints表名称(专门存储各个表的约束)
SELECT * FROM information_schema.table_constraints
WHERE table_name = '表名称';

2. 非空约束

1) 作用

限定某个字段/ 某列的值不允许为空

2) 关键字

NOT NULL

3) 特点

  • 默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型
  • 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
  • 一个表可以有很多列都分别限定了非空
  • 空字符串’'不等于NULL,0也不等于NULL

4) 添加非空约束

1. 建表时

CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型 NOT NULL,
字段名 数据类型 NOT NULL
);

2. 建表后

alter table 表名称 modify 字段名 数据类型 not null;

5) 删除非空约束

alter table 表名称 modify 字段名 数据类型 NULL;#去掉not null,相当于修改某个非注解字段,该字段允许为空
或
alter table 表名称 modify 字段名 数据类型;#去掉not null,相当于修改某个非注解字段,该字段允许为空

3. 唯一性约束

1) 作用

用来限制某个字段/某列的值不能重复

2) 关键字

UNIQUE

3) 特点

  • 同一个表可以有多个唯一约束
  • 唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯一
  • 唯一性约束允许列值为空
  • 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同
  • MySQL会给唯一约束的列上默认创建一个唯一索引

4) 添加唯一约束

1. 建表时

create table 表名称(
字段名 数据类型,
字段名 数据类型 unique,
字段名 数据类型 unique key,
字段名 数据类型
);

create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] unique key(字段名)
);

举例:

CREATE TABLE USER(
id INT NOT NULL,
NAME VARCHAR(25),
PASSWORD VARCHAR(16),
-- 使用表级约束语法
CONSTRAINT uk_name_pwd UNIQUE(NAME,PASSWORD)
);

2. 建表后指定唯一键约束

#字段列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的
#方式1:
alter table 表名称 add unique key(字段列表);
#方式2:
alter table 表名称 modify 字段名 字段类型 unique;

5) 关于复合唯一约束

create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
unique key(字段列表) #字段列表中写的是多个字段名,多个字段名用逗号分隔,表示那么是复合唯一,即多
个字段的组合是唯一的
);

6) 删除唯一约束

  • 添加唯一性约束的列上也会自动创建唯一索引
  • 删除唯一约束只能通过删除唯一索引的方式删除
  • 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样
  • 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同
  • 如果是组合列,那么默认和() 中排在第一个的列名相同。也可以自定义唯一性约束名
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名'; #查看都有哪些约束
ALTER TABLE USER
DROP INDEX uk_name_pwd;

4. PRIMARY KEY 约束

1) 作用

用来唯一标识表中的一行记录。

2) 关键字

primary key

3) 特点

主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值

  • 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建
  • 主键约束对应着表中的一列或者多列(复合主键)
  • 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复
  • MySQL的主键名总是PRIMARY,就算自己命名了主键约束名也没用
  • 当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高。如果删除主键约束了,主键约束对应的索引就自动删除了
  • 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性

4) 添加主键约束

1. 建表时指定主键约束

create table 表名称(
字段名 数据类型 primary key, #列级模式
字段名 数据类型,
字段名 数据类型
);

create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] primary key(字段名) #表级模式
);

2. 建表后增加主键约束

ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表); #字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键

5) 关于复合主键

create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
primary key(字段名1,字段名2) #表示字段1和字段2的组合是唯一的,也可以有更多个字段
);

6) 删除主键约束

alter table 表名称 drop primary key

说明:删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空还存在

5. 自增列:AUTO_INCREMENT

1) 作用

某个字段的值自增

2) 关键字

auto_increment

3) 特点

  • 一个表最多只能有一个自增长列

  • 当需要产生唯一标识符或顺序值时,可设置自增长

  • 自增长列约束的列必须是键列(主键列,唯一键列)

  • 自增约束的列的**数据类型必须是整数类型 **

  • 如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值

4) 如何指定自增约束

1. 建表时

create table 表名称(
字段名 数据类型 primary key auto_increment,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);
create table 表名称(
字段名 数据类型 default 默认值 ,
字段名 数据类型 unique key auto_increment,
字段名 数据类型 not null default 默认值,
primary key(字段名)
);

2. 建表后

alter table 表名称 modify 字段名 数据类型 auto_increment;

5) 删除自增约束

#alter table 表名称 modify 字段名 数据类型 auto_increment;#给这个字段增加自增约束
alter table 表名称 modify 字段名 数据类型; #去掉auto_increment相当于删除

6. FOREIGN KEY 约束

1) 作用

限定某个表的某个字段的引用完整性。

2) 关键字

FOREIGN KEY

3) 主表和从表/父表和子表

主表(父表):被引用的表,被参考的表

从表(子表):引用别人的表,参考别人的表

4) 特点

  • 从表的外键列,必须引用/参考主表的主键或唯一约束的列

  • 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名(例如 student_ibfk_1;),也可以指定外键约束名。

  • 创建(CREATE)表时就指定外键约束的话,先创建主表,**再创建从表 **

  • 删表时,先删从表(或先删除外键约束),**再删除主表 **

  • 当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据

  • 在从表中指定外键约束,并且**一个表可以建立多个外键约束 **

  • 从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。

  • 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引,索引名是外键的约束名

  • 删除外键约束后,必须手动删除对应的索引

5) 添加外键约束

1. 建表时

create table 主表名称(
字段1 数据类型 primary key,
字段2 数据类型
);

create table 从表名称(
字段1 数据类型 primary key,
字段2 数据类型,
[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段)
);
#(从表的某个字段)的数据类型必须与主表名(被参考字段)的数据类型一致,逻辑意义也一样
#(从表的某个字段)的字段名可以与主表名(被参考字段)的字段名一样,也可以不一样
-- FOREIGN KEY: 在表级指定子表中的列
-- REFERENCES: 标示在父表中的列
create table dept( #主表
did int primary key, #部门编号
dname varchar(50) #部门名称
);
create table emp(#从表
eid int primary key, #员工编号
ename varchar(5), #员工姓名
deptid int, #员工所在的部门
foreign key (deptid) references dept(did) #在从表中指定外键约束
#emp表的deptid和和dept表的did的数据类型一致,意义都是表示部门的编号
);
说明:
(1)主表dept必须先创建成功,然后才能创建emp表,指定外键成功。
(2)删除表时,先删除从表emp,再删除主表dept

2. 建表后

需要修改表的设计(比如添加新的字段,增加新的关联关系),但没有预先定义外键约束,用修改表的方式来补充定义

格式:

ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用字段) [on update xx][on delete xx];

举例:

ALTER TABLE emp1
ADD [CONSTRAINT emp_dept_id_fk] FOREIGN KEY(dept_id) REFERENCES dept(dept_id);

6) 约束等级

  • Cascade方式 :在父表上update/delete记录时,同步update/delete掉子表的匹配记录
  • Set null方式 :在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子 表的外键列不能为not null
  • No action方式 :如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
  • Restrict方式 :同no action, 都是立即检查外键约束
  • Set default方式 (在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置 成一个默认的值,但Innodb不能识别x

如果没有指定等级,就相当于Restrict方式。 对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT 的方式

7) 删除外键约束

流程如下:

(1)第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';  #查看某个表的约束名
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;

(2)第二步查看索引名和删除索引。(注意,只能手动删除)
SHOW INDEX FROM 表名称; #查看某个表的索引名
ALTER TABLE 从表名 DROP INDEX 索引名;

7. CHECK 约束

1) 作用

检查某个字段的值是否符号xx要求,一般指的是值的范围

2) 关键字

CHECK

3) 说明

MySQL 8.0中可以使用check约束了

create table employee(
eid int primary key,
ename varchar(5),
gender char check ('男' or '女')
);

8. DEFAULT约束

1) 作用

给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值

2) 关键字

DEFAULT

3) 添加默认值

1. 建表时

create table 表名称(
字段名 数据类型 primary key,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);

2. 建表后

alter table 表名称 modify 字段名 数据类型 default 默认值;
#如果这个字段原来有非空约束,你还保留非空约束,那么在加默认值约束时,还得保留非空约束,否则非空约束就被删除了
#同理,在给某个字段加非空约束也一样,如果这个字段原来有默认值约束,你想保留,也要在modify语句中保留默认值约束,否则就删除了
alter table 表名称 modify 字段名 数据类型 default 默认值 not null;

删除默认值

alter table 表名称 modify 字段名 数据类型; #删除默认值约束,也不保留非空约束
alter table 表名称 modify 字段名 数据类型 not null; #删除默认值约束,保留非空约束

9. 面试

  1. 面试1、为什么建表时,加 not null default ‘’ 或 default 0

    • 答:
    • 不想让表中出现null值
  2. 面试2、为什么不想要 null 的值

    • 答:

    • (1)不好比较。null是一种特殊值,比较时只能用专门的is null 和 is not null来比较。碰到运算符,通常返回null

    • (2)效率不高。影响提高索引效果。因此,我们往往在建表时 not null default ‘’ 或 default

  3. 面试3、带AUTO_INCREMENT约束的字段值是从1开始的吗?

    • 答 :
    • 在MySQL中,默认AUTO_INCREMENT的初始 值是1,每新增一条记录,字段值自动加1。设置自增属(AUTO_INCREMENT)的时候,还可以指定第 一条插入记录的自增字段的值,这样新插入的记录的自增字段值从初始值开始递增,如在表中插入第一 条记录,同时指定id值为5,则以后插入的记录的id值就会从6开始往上增加。添加主键约束时,往往需要 设置字段自动增加属性
  4. 面试4、并不是每个表都可以任意选择存储引擎?

    • 答:
    • 外键约束(FOREIGN KEY)不能跨引擎使用,MySQL支持多种存储引擎,每一个表都可以指定一个不同的存储引擎,需要注意的是:外键约束是用来 保证数据的参照完整性的,如果表之间需要关联外键,却指定了不同的存储引擎,那么这些表之间是不 能创建外键约束的。所以说,存储引擎的选择也不完全是随意的

十二、视图

1. 常见的数据库对象

对象 描述
表(TABLE) 表是存储数据的逻辑单元,以行和列的形式存在,列是字段,行是记录
数据字典 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护, 程序员通常不应该修改,只可查看
约束 (CONSTRAINT) 执行数据校验的规则,用于保证数据完整性的规则
视图(VIEW) 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据
索引(INDEX) 用于提高查询性能,相当于书的目录
存储过程 (PROCEDURE) 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境
存储函数 (FUNCTION) 用于完成一次特定的计算,具有一个返回值
触发器 (TRIGGER) 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理

2. 视图概述

  • 视图是一种虚拟表 ,本身是不具有数据的,占用很少的内存空间,它是 SQL 中的一个重要概念

  • 视图建立在已有表的基础上, 视图赖以建立的这些表称为基表

    mysql基础学习笔记_第6张图片

  • 视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和 修改操作时,数据表中的数据会相应地发生变化

  • 视图提供数据内容的语句为 SELECT 语句, 可以将视图理解为存储起来的 SELECT 语句

    • 在数据库中,视图不会保存数据,数据真正保存在数据表中。当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化;反之亦然
  • 视图,是向用户提供基表数据的另一种表现形式,可以帮助把经常查询的结果集放到虚拟表中,提升使用效率

3. 创建视图

  • CREATE VIEW 语句中嵌入子查询
CREATE [OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(字段列表)]
AS 查询语句
[WITH [CASCADED|LOCAL] CHECK OPTION]
  • 精简版
CREATE VIEW 视图名称
AS 查询语句

1) 创建单表视图

举例:

# 方式一:
CREATE VIEW empid80
AS
SELECT employee_id, last_name, salary
FROM employees
WHERE department_id = 80;

# 方式二:
CREATE VIEW empsalary8000(emp_id, NAME, monthly_sal) # 小括号内字段个数与SELECT中字段个数相同
AS
SELECT employee_id, last_name, salary
FROM employees
WHERE salary > 8000;

查询视图:

SELECT *
FROM empid80;

2) 创建多表联合视图

举例:

CREATE VIEW empview
AS
SELECT employee_id emp_id,last_name NAME,department_name
FROM employees e,departments d
WHERE e.department_id = d.department_id;
CREATE VIEW dept_sum_vu
(name, minsal, maxsal, avgsal)
AS
SELECT d.department_name, MIN(e.salary), MAX(e.salary),AVG(e.salary)
FROM employees e, departments d
WHERE e.department_id = d.department_id
GROUP BY d.department_name;
  • 利用视图对数据进行格式化

常需要输出某个格式的内容,比如输出员工姓名和对应的部门名,对应格式为 emp_name(department_name),就可以使用视图来完成数据格式化的操作:

CREATE VIEW emp_depart
AS
SELECT CONCAT(last_name,'(',department_name,')') AS emp_dept
FROM employees e JOIN departments d
WHERE e.department_id = d.department_id;

3) 基于视图创建视图

当创建好一张视图之后,可以在它的基础上继续创建视图

举例:联合“emp_dept”视图和“emp_year_salary”视图查询员工姓名、部门名称、年薪信息创建 “emp_dept_ysalary”视图

CREATE VIEW emp_dept_ysalary
AS
SELECT emp_dept.ename,dname,year_salary
FROM emp_dept INNER JOIN emp_year_salary
ON emp_dept.ename = emp_year_salary.ename;

4. 查看视图

语法1:查看数据库的表对象视图对象

SHOW TABLES;

语法2:查看视图的结构

DESC  视图名称;

语法3:查看视图的属性信息(显示数据表的存储引擎、版本、数据行数和数据大小等)

SHOW TABLE STATUS LIKE '视图名称'\G

语法4:查看视图的详细定义信息

SHOW CREATE VIEW 视图名称;

5. 更新视图的数据

1) 一般情况

MySQL支持使用INSERT、UPDATE和DELETE语句对视图中的数据进行插入、更新和删除操作。当视图中的数据发生变化时,数据表中的数据也会发生变化

举例:UPDATE操作

UPDATE emp_tel SET tel = '123456' WHERE ename = '孙洪亮';

举例:DELETE操作

 DELETE FROM emp_tel WHERE ename = '孙洪亮';

2) 不可更新的视图

要使视图可更新,视图中的行和底层基本表中的行之间必须存在 一对一 的关系。另外当视图定义出现如下情况时,视图不支持更新操作:

  • 在定义视图的时候指定了“ALGORITHM = TEMPTABLE”,视图将**不支持INSERT、DELETE **
  • 视图中不包含基表中所有被定义为非空又未指定默认值的列,视图将不支持INSERT
  • 定义视图的SELECT语句中使用了 JOIN联合查询 ,视图将不支持INSERT、DELETE
  • 定义视图的SELECT语句后的字段列表中使用了 数学表达式 或 子查询 ,视图将不支持INSERT,也不支持UPDATE使用了数学表达式、子查询的字段值
  • 定义视图的SELECT语句后的字段列表中使用 DISTINCT 、 聚合函数 、 GROUP BY 、 HAVING 、 UNION 等,视图将不支持INSERT、UPDATE、DELETE
  • 定义视图的SELECT语句中包含了子查询,而子查询中引用了FROM后面的表,视图将不支持 INSERT、UPDATE、DELETE
  • 视图定义基于一个 不可更新视图,常量视图

视图作为虚拟表 ,主要用于方便查询 ,不建议更新视图的数据。对视图数据的更改,都是通过对实际数据表里数据的操作来完成的

6. 修改、删除视图

1) 修改视图

方式1:使用CREATE OR REPLACE VIEW 子句修改视图

CREATE OR REPLACE VIEW empvu80
(id_number, name, sal, department_id)
AS
SELECT employee_id, first_name || ' ' || last_name, salary, department_id
FROM employees
WHERE department_id = 80;

说明:CREATE VIEW 子句中各列的别名应和子查询中各列相对应。

方式2:ALTER VIEW

修改视图的语法是:

ALTER VIEW 视图名称
AS
查询语句

2) 删除视图

  • 删除视图只是删除视图的定义,并不会删除基表的数据
  • 语法:
DROP VIEW IF EXISTS 视图名称;
  • 举例:
DROP VIEW empvu80;

基于视图a、b创建了新的视图c,如果将视图a或者视图b删除,会导致视图c的查询失败。这样的视图c需要手动删除或修改,否则影响使用

7. 总结

1) 优点

1. 操作简单

将经常使用的查询操作定义为视图,可以使开发人员不需要关心视图对应的数据表的结构、表与表之间的关联关系,也不需要关心数据表之间的业务逻辑和查询条件,而只需要简单地操作视图即可,极大简化了对数据库的操作

2. 减少数据冗余

视图跟实际数据表不一样,它存储的是查询语句,视图本身不存储数据,不占用数据存储的资源,减少了数据冗余

3. 数据安全

MySQL将用户对数据的访问限制在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用户不必直接查询或操作数据表。可以理解为视图具有隔离性,视图相当于在用户和实际的数据表之间加了一层虚拟表,同时,MySQL可以根据权限将用户对数据的访问限制在某些视图上,用户不需要查询数据表,可以直接通过视图获取数据表中的信息。这在一定程度上保障了数据表中数据的安全性

4. 适应灵活多变的需求

当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较大,可以使用视图来减少改动的工作量

5. 能够分解复杂的查询逻辑

数据库中如果存在复杂的查询逻辑,则可以将问题进行分解,创建多个视图获取数据,再将创建的多个视图结合起来,完成复杂的查询逻辑

2) 不足

在实际数据表的基础上创建了视图,如果实际数据表的结构变更,则需要及时对相关的视图进行相应的维护。特别是嵌套的视图(在视图的基础上创建视图),维护比较复杂, 可读性不好 ,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包含复杂的逻辑,这些都会增加维护的成本

十三、存储过程与存储函数

存储过程和函数能够将复杂的SQL逻辑封装在一起,应用程序无须关注存储过程和函数内部复杂的SQL逻辑,而只需要简单地调用存储过程和函数即可

1. 存储过程(Stored Procedure)概述

1) 理解

**含义:**存储过程是一组经过预先编译的SQL语句的封装

执行过程:存储过程预先存储在MySQL服务器上,需要执行的时候,客户端只需要向服务器端发出调用存储过程的命令,服务器端就可以把预先存储好的这一系列SQL语句全部执行

好处:

  1. 简化操作,提高了sql语句的重用性,减少了开发程序员的压力
  2. 减少操作过程中的失误,提高效率
  3. 减少网络传输量(客户端不需要把所有的 SQL 语句通过网络发给服务器)
  4. 减少了 SQL 语句暴露在网上的风险,也提高了数据查询的安全性

和视图、函数的对比:

视图:

  • 和视图有着同样的优点,清晰、安全,还可以减少网络传输量
  • 和视图不同,视图是虚拟表 ,通常不对底层数据表直接操作,而存储过程是程序化的 SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理

函数:

  • 一旦存储过程被创建出来,使用直接通过调用存储过程名即可。相较于函数,存储过程是没有返回值的

2) 分类

存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:

  1. 没有参数(无参数无返回)

  2. 仅仅带 IN 类型(有参数无返回)

  3. 仅仅带 OUT 类型(无参数有返回)

  4. 既带 IN 又带 OUT(有参数有返回)

  5. 带 INOUT(有参数有返回)

注意:IN、OUT、INOUT 都可以在一个存储过程中带多个。

2. 创建存储过程

1) 语法分析

语法:

CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END

说明:

1、参数前面的符号的意思

  • IN :当前参数为输入参数,表示入参;

    存储过程只是读取这个参数的值。如果没有定义参数种类, 默认就是 IN ,表示输入参数

  • OUT :当前参数为输出参数,也就是表示出参;

    执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值

  • INOUT :当前参数既可以为输入参数,也可以为输出参数

2、形参类型可以是MySQL数据库中的任意类型

3、characteristics 表示创建存储过程时指定的对存储过程的约束条件,其取值信息如下:

LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
  • LANGUAGE SQL :说明存储过程执行体是由SQL语句组成的,当前系统支持的语言为SQL

  • [NOT] DETERMINISTIC :指明存储过程执行的结果是否确定。DETERMINISTIC表示结果是确定。每次执行存储过程时,相同的输入会得到相同的输出NOT DETERMINISTIC表示结果是不确定的,相同的输入可能得到不同的输出。如果没有指定任意一个值,默认为NOT DETERMINISTIC

  • { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } :指明子程序使用SQL语句的限制

    • CONTAINS SQL表示当前存储过程的子程序包含SQL语句,但是并不包含读写数据的SQL语句
    • NO SQL表示当前存储过程的子程序中不包含任何SQL语句
    • READS SQL DATA表示当前存储过程的子程序中包含读数据的SQL语句
    • MODIFIES SQL DATA表示当前存储过程的子程序中包含写数据的SQL语句
    • 默认情况下,系统会指定为CONTAINS SQL
  • SQL SECURITY { DEFINER | INVOKER } :执行当前存储过程的权限,即指明哪些用户能够执行当前存储过程

    • DEFINER 表示只有当前存储过程的创建者或者定义者才能执行当前存储过程
    • INVOKER 表示拥有当前存储过程的访问权限的用户能够执行当前存储过程
  • COMMENT ‘string’ :注释信息,可以用来描述存储过程

4、存储过程体中可以有多条 SQL 语句,如果仅仅一条SQL 语句,则可以省略 BEGIN 和 END

  • BEGIN…END:BEGIN…END 中间包含了多个语句,每个语句都以(;)号为结束符
  • DECLARE:用来声明变量,使用的位置在于 BEGIN…END 语句中间,需要在其他语句使用之前进行变量的声明
  • SET:赋值语句,用于对变量进行赋值
  • SELECT… INTO:把从数据表中查询的结果存放到变量中,为变量赋值

5、需要设置新的结束标记

  • DELIMITER 新的结束标记

  • 因为MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用DELIMITER改变存储过程的结束符

  • 比如:“DELIMITER //”语句的作用是将MySQL的结束符设置为//,并以“END //”结束存储过程。存储过程定义完毕之后再使用“DELIMITER ;”恢复默认结束符

  • 当使用DELIMITER命令时,应该避免使用反斜杠(‘\’)字符,因为反斜线是MySQL的转义字符

  • 示例:

  • DELIMITER $
    CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
    [characteristics ...]
    BEGIN
    sql语句1;
    sql语句2;
    END $
    

2) 代码举例

举例1:创建存储过程select_all_data(),查看 emps 表的所有数据

DELIMITER $
CREATE PROCEDURE select_all_data()
BEGIN
SELECT * FROM emps;
END $
DELIMITER ;

举例2:创建存储过程avg_employee_salary(),返回所有员工的平均工资

DELIMITER //
CREATE PROCEDURE avg_employee_salary ()
BEGIN
SELECT AVG(salary) AS avg_salary FROM emps;
END //
DELIMITER ;

3. 调用存储过程

1) 调用格式

存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行其他数据库中的存储过程,需要指定数据库名称,例如CALL dbname.procname

CALL 存储过程名(实参列表)

格式:

1、调用in模式的参数:

CALL sp1('值');

2、调用out模式的参数:

SET @name;
CALL sp1(@name);
SELECT @name;

3、调用inout模式的参数:

SET @name=值;
CALL sp1(@name);
SELECT @name;

2) 代码举例

举例1:

DELIMITER //
CREATE PROCEDURE CountEmp(IN deptid INT,OUT num INT)
BEGIN
SELECT COUNT(*) INTO num FROM emp
WHERE dept_id = deptid;
END //
DELIMITER ;

调用存储过程:

CALL CountEmp (20, @num);

查看返回结果:

SELECT @num;

**举例2:**创建存储过程,实现累加运算,计算 1+2+…+n 等于多少

DELIMITER //
CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
DECLARE i INT;
DECLARE sum INT;
SET i = 1;
SET sum = 0;
WHILE i <= n DO
SET sum = sum + i;
SET i = i +1;
END WHILE;
SELECT sum;
END //
DELIMITER ;

调用,求1+…+100

CALL add_num(100);

4. 存储函数的使用

1) 语法分析

学过的函数:LENGTH、SUBSTR、CONCAT等

语法格式:

CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics ...]
BEGIN
函数体 #函数体中肯定有 RETURN 语句
END

说明:

  1. 参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE是合法的,FUNCTION中总是默认为IN参数

  2. RETURNS type 语句表示函数返回数据的类型; RETURNS子句只能对FUNCTION做指定,对函数而言这是强制的。用来指定函数的返回类型,而且函数体必须包含一个RETURN value 语句

  3. characteristic 创建函数时指定的对函数的约束,取值与创建存储过程时相同

  4. 函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,可以省略 BEGIN…END

2) 调用存储函数

用户自己定义的存储函数与MySQL内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是MySQL的开发者定义

SELECT 函数名(实参列表)

3) 代码举例

举例1:

创建存储函数,名称为salary_by_name(),参数定义为空,该函数查询关羽的salary,并返回数据类型为字符串型

DELIMITER //
CREATE FUNCTION salary_by_name()
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT salary FROM emp WHERE ename = '关羽');
END //
DELIMITER ;

调用:

SELECT salary_by_name();

举例2:

创建存储函数,名称为email_by_id(),参数传入emp_id,该函数查询emp_id的email,并返回数据类型为字符串型

DELIMITER //
CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM employees WHERE employee_id = emp_id);
END //
DELIMITER ;

调用:

SET @emp_id = 102;
SELECT email_by_id(@emp_id);

注意:

若在创建存储函数中报错“ you might want to use the less safe log_bin_trust_function_creators variable ”,有两种处理方法:

  • 方式1:

    加上必要的函数特性“[NOT] DETERMINISTIC”和“{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}”

  • 方式2:

SET GLOBAL log_bin_trust_function_creators = 1;

4) 对比存储函数与存储过程

关键字 调用语法 返回值 应用场景
存储过程 PROCEDURE CALL 存储过程() 有0个或多个 一般用于更新
存储函数 FUNCTION SELECT 函数 () 只能是一个 一般用于查询结果为一个值并返回时

存储函数可以放在查询语句中使用,存储过程不行。存储过程的功能更加强大,包括能够执行对表的操作(比如创建表,删除表等)和事务操作

5. 存储过程和函数的查看、修改、删除

1) 查看

可以使用SHOW STATUS语句或SHOW CREATE语句来查看,也可直接从系统的information_schema数据库中查询

  1. 使用SHOW CREATE语句查看存储过程和函数的创建信息
SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名
  1. 使用SHOW STATUS语句查看存储过程和函数的状态信息
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
  1. 从information_schema.Routines表中查看存储过程和函数的信息

MySQL中存储过程和函数的信息存储在information_schema数据库下的Routines表中。可以通过查询该表的记录来查询存储过程和函数的信息

SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='存储过程或函数的名' [AND ROUTINE_TYPE = {'PROCEDURE|FUNCTION'}];

说明:如果在MySQL数据库中存在存储过程和函数名称相同的情况,可以指定ROUTINE_TYPE查询条件来指明查询的是存储过程还是函数。

2) 修改

使用ALTER修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性

ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]

其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有不同

{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
  • CONTAINS SQL ,表示子程序包含SQL语句,但不包含读或写数据的语句

  • NO SQL ,表示子程序中不包含SQL语句

  • READS SQL DATA ,表示子程序中包含读数据的语句

  • MODIFIES SQL DATA ,表示子程序中包含写数据的语句

  • SQL SECURITY { DEFINER | INVOKER } ,指明谁有权限来执行

    • DEFINER ,表示只有定义者自己才能够执行
    • INVOKER ,表示调用者可以执行
  • COMMENT ‘string’ ,表示注释信息

修改存储过程使用ALTER PROCEDURE语句,修改存储函数使用ALTER FUNCTION语句

3) 删除

删除存储过程和函数,可以使用DROP语句,其语法结构如下:

DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名

6. 关于存储过程使用的争议

1) 优点

  1. 存储过程可以一次编译多次使用。存储过程只在创建时进行编译,之后的使用都不需要重新编译, 提升了 SQL 的执行效率

  2. 可以减少开发工作量。将代码封装成模块,实际上是编程的核心思想之一,可以把复杂的问题拆解成不同的模块,模块之间可以重复使用 ,在减少开发工作量的同时,保证代码的结构清晰

  3. 存储过程的安全性强。设定存储过程的时候可以设置对用户的使用权限 ,和视图一样具有较强的安全性。

  4. 可以减少网络传输量。因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减少了网络传输量。

  5. 良好的封装性。在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成了一次存储过程,只需要连接一次即可

2) 缺点

阿里开发规范 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性

  1. 可移植性差。存储过程不能跨数据库移植,比如在MySQL、Oracle 和 SQL Server里编写的存储过程,在换成其他数据库时都需要重新编写

  2. 调试困难。只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程,开发和维护都不容易

  3. 存储过程的版本管理很困难。比如数据表索引发生变化了,可能会导致存储过程失效。在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦

  4. 它不适合高并发的场景。高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护, 增加数据库的压力

十四、变量、流程控制与游标

1. 变量

在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终 的结果数据

在 MySQL 数据库中,变量分为系统变量以及用户自定义变量

1) 系统变量

系统变量分类

变量由系统定义,不是用户定义,属于服务器层面。启动MySQL服务,生成MySQL服务实例期间, MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是编译MySQL时参数的默认值,要么是配置文件 (例如my.ini等)中的参数值

系统变量分为全局系统变量(需要添加 global 关键字)以及会话系统变量(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。如果不写,默认会话级别。静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。

每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制

  • 全局系统变量针对于所有会话(连接)有效,但不能跨重启
  • 会话系统变量仅针对于当前会话(连接)有效。会话期间,当前会话对某个会话系统变量值的修改,不会影响其他会话同一个会话系统变量的值
  • 会话1对某个全局系统变量值的修改会导致会话2中同一个全局系统变量值的修改

在MySQL中

  • 有些系统变量只能是全局的,例如 max_connections 用于限制服务器的最大连接数
  • 有些系统变量作用域既可以是全局又可以是会话,例如 character_set_client 用于设置客户端的字符集
  • 有些系 统变量的作用域只能是当前会话,例如 pseudo_thread_id 用于标记当前会话的 MySQL 连接 ID

查看系统变量

  • 查看所有或部分系统变量
#查看所有全局变量
SHOW GLOBAL VARIABLES;
#查看所有会话变量
SHOW SESSION VARIABLES;
或
SHOW VARIABLES;
#查看满足条件的部分系统变量。
SHOW GLOBAL VARIABLES LIKE '%标识符%';
#查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%标识符%';

查看指定系统变量

作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头

  • “@@global”仅用于标记全局系统变量
  • “@@session”仅用于标记会话系统变量
  • “@@”首先标记会话系统变量,如果会话系统变量不存在, 则标记全局系统变量
#查看指定的系统变量的值
SELECT @@global.变量名;
#查看指定的会话变量的值
SELECT @@session.变量名;
#或者
SELECT @@变量名;

修改系统变量的值

有些时候,数据库管理员需要修改系统变量的默认值,以便修改当前会话或者MySQL服务实例的属性、 特征。具体方法:

  • 方式1:修改MySQL 配置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)

  • 方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值

#为某个系统变量赋值
#方式1:
SET @@global.变量名=变量值;
#方式2:
SET GLOBAL 变量名=变量值;
#为某个会话变量赋值
#方式1:
SET @@session.变量名=变量值;
#方式2:
SET SESSION 变量名=变量值;

2) 用户变量

用户变量分类

用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@” 开头。根据作用范围不同,分为会话用户变量和局部变量

  • 会话用户变量:作用域和会话变量一样,只对当前连接会话有效
  • 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用

会话用户变量

  • 变量的定义
#方式1:“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
#方式2:“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
  • 查看用户变量的值 (查看、比较、运算等)
SELECT @用户变量

局部变量

定义:可以使用 DECLARE 语句定义一个局部变量

作用域:仅仅在定义它的 BEGIN … END 中有效

位置:只能放在 BEGIN … END 中,而且只能放在第一句

BEGIN
#声明局部变量
DECLARE 变量名1 变量数据类型 [DEFAULT 变量默认值];
DECLARE 变量名2,变量名3,... 变量数据类型 [DEFAULT 变量默认值];
#为局部变量赋值
SET 变量名1 = 值;
SELECT 值 INTO 变量名2 [FROM 子句];
#查看局部变量的值
SELECT 变量1,变量2,变量3;
END
  1. 定义变量
DECLARE 变量名 类型 [default 值]; # 如果没有DEFAULT子句,初始值为NULL
  1. 变量赋值

方式1:一般用于赋简单的值

SET 变量名=值;
SET 变量名:=值;

方式2:一般用于赋表中的字段值

SELECT 字段名或表达式 INTO 变量名 FROM 表;
  1. 使用变量 (查看、比较、运算等)
SELECT 局部变量名;

举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary

DELIMITER //
CREATE PROCEDURE set_value()
BEGIN
DECLARE emp_name VARCHAR(25);
DECLARE sal DOUBLE(10,2);
SELECT last_name, salary INTO emp_name,sal
FROM employees
WHERE employee_id = 102;
SELECT emp_name, sal;
END //
DELIMITER ;

举例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)

#方式1:使用用户变量
SET @m=1;
SET @n=1;
SET @sum=@m+@n;
SELECT @sum;
#方式2:使用局部变量
DELIMITER //
CREATE PROCEDURE add_value()
BEGIN
#局部变量
DECLARE m INT DEFAULT 1;
DECLARE n INT DEFAULT 3;
DECLARE SUM INT;
SET SUM = m+n;
SELECT SUM;
END //
DELIMITER ;

对比会话用户变量与局部变量

作用域 定义位置 语法
会话用户变量 当前会话 会话的任何地方 加@符号,不用指定类型
局部变量 定义它的BEGIN END中 BEGIN END的第一句话 一般不用加@,需要指定类型

2. 定义条件与处理程序

定义条件:是事先定义程序执行过程中可能遇到的问题

处理程序:定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。

说明:定义条件和处理程序在存储过程、存储函数中都是支持的

1) 案例分析

案例分析:创建一个名称为“UpdateDataNoCondition”的存储过程

DELIMITER //
CREATE PROCEDURE UpdateDataNoCondition()
BEGIN
SET @x = 1;
UPDATE employees SET email = NULL WHERE last_name = 'Abel';
SET @x = 2;
UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
SET @x = 3;
END //
DELIMITER ;

调用存储过程:

mysql> CALL UpdateDataNoCondition();
ERROR 1048 (23000): Column 'email' cannot be null
mysql> SELECT @x;
+------+
| @x |
+------+
| 1 |
+------+
1 row in set (0.00 sec)

此时@x变量的值为1。结合创建存储过程的SQL语句代码得出:在存储过程中未定义条件和处理程序,且当存储过程中执行的SQL语句报错时,MySQL数据库会抛出错误,并退出当前SQL逻辑, 不再向下继续执行

2) 定义条件

定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个 错误名字 和 指定的 错误条件 关联起来。这个名字可以随后被用在定义处理程序的 DECLARE HANDLER 语句中

定义条件使用DECLARE语句,语法格式如下:

DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)

错误码的说明:

  • MySQL_error_code 和 sqlstate_value 都可以表示MySQL的错误
    • MySQL_error_code是数值类型错误代码
    • sqlstate_value是长度为5的字符串类型错误代码

例如,在ERROR 1418 (HY000)中,1418是MySQL_error_code,'HY000’是sqlstate_value

例如,在ERROR 1142(42000)中,1142是MySQL_error_code,'42000’是sqlstate_value

举例1:定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型是“ERROR 1048 (23000)”对应

#使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
#使用sqlstate_value
DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';

3) 定义处理程序

可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。使用DECLARE语句定义处理程序

DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
  • 处理方式:处理方式有3个取值:CONTINUE、EXIT、UNDO

    • CONTINUE :表示遇到错误不处理,继续执行
    • EXIT :表示遇到错误马上退出
    • UNDO :表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作
  • 错误类型(即条件)可以有如下取值:

    • SQLSTATE ‘字符串错误码’ :表示长度为5的sqlstate_value类型的错误代码;
    • MySQL_error_code :匹配数值类型错误代码
    • 错误名称 :表示DECLARE … CONDITION定义的错误条件名称
    • SQLWARNING :匹配所有以01开头的SQLSTATE错误代码
    • NOT FOUND :匹配所有以02开头的SQLSTATE错误代码
    • SQLEXCEPTION :匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码
  • 处理语句:如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是像“ SET 变量 = 值 ”这样的简单语句,也可以是使用 BEGIN … END 编写的复合语句

定义处理程序的方式

#方法1:捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';
#方法2:捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
#方法3:先定义条件,再调用
DECLARE no_such_table CONDITION FOR 1146;
DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
#方法4:使用SQLWARNING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
#方法5:使用NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
#方法6:使用SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';

4) 案例解决

在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为1048时,执行 CONTINUE操作,并且将@proc_value的值设置为-1

DELIMITER //
CREATE PROCEDURE UpdateDataNoCondition()
BEGIN
    #定义处理程序
    DECLARE CONTINUE HANDLER FOR 1048 SET @proc_value = -1;
    SET @x = 1;
    UPDATE employees SET email = NULL WHERE last_name = 'Abel';
    SET @x = 2;
    UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
    SET @x = 3;
END //
DELIMITER ;

3. 流程控制

解决复杂问题需要执行多个 SQL 操作完成,流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:

  • 顺序结构 :程序从上往下依次执行
  • 分支结构 :程序按条件进行选择执行,从两条或多条路径中选择一条执行
  • 循环结构 :程序满足一定条件下,重复执行一组语句

针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。

  • 条件判断语句 :IF 语句和 CASE 语句
  • 循环语句 :LOOP、WHILE 和 REPEAT 语句
  • 跳转语句 :ITERATE 和 LEAVE 语句

1) 分支结构之 IF

  • IF 语句的语法结构是:
IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF

根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。

  • 特点:

    1. 不同的表达式对应不同的操作
    2. 使用在begin end中
  • 举例1:

IF val IS NULL
	THEN SELECT 'val is null';
ELSE SELECT 'val is not null';
END IF;
  • 举例2:声明存储过程“update_salary_by_eid1”,定义IN参数emp_id,输入员工编号。判断该员工薪资如果低于8000元并且入职时间超过5年,就涨薪500元;否则就不变
DELIMITER //
CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)
BEGIN
    DECLARE emp_salary DOUBLE;
    DECLARE hire_year DOUBLE;
    SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id;
    SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year
    FROM employees WHERE employee_id = emp_id;
    IF emp_salary < 8000 AND hire_year > 5
    THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
    END IF;
END //
DELIMITER ;

2) 分支结构之 CASE

  • CASE 语句的语法结构1:
#情况一:类似于switch
CASE 表达式
WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
  • CASE 语句的语法结构2:
#情况二:类似于多重if
CASE
WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
  • 举例1:使用CASE流程控制语句的第1种格式,判断val值等于1、等于2,或者两者都不等
CASE val
    WHEN 1 THEN SELECT 'val is 1';
    WHEN 2 THEN SELECT 'val is 2';
    ELSE SELECT 'val is not 1 or 2';
END CASE;
  • 举例2:声明存储过程“update_salary_by_eid4”,定义IN参数emp_id,输入员工编号。判断该员工 薪资如果低于9000元,就更新薪资为9000元;薪资大于等于9000元且低于10000的,但是奖金比例 为NULL的,就更新奖金比例为0.01;其他的涨薪100元
DELIMITER //
CREATE PROCEDURE update_salary_by_eid4(IN emp_id INT)
BEGIN
    DECLARE emp_sal DOUBLE;
    DECLARE bonus DECIMAL(3,2);
    SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
    SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
    CASE
    WHEN emp_sal<9000
    	THEN UPDATE employees SET salary=9000 WHERE employee_id = emp_id;
    WHEN emp_sal<10000 AND bonus IS NULL
    	THEN UPDATE employees SET commission_pct=0.01 WHERE employee_id = emp_id;
    ELSE
    	UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
    END CASE;
END //
DELIMITER ;
  • 举例3:声明存储过程update_salary_by_eid5,定义IN参数emp_id,输入员工编号。判断该员工的 入职年限,如果是0年,薪资涨50;如果是1年,薪资涨100;如果是2年,薪资涨200;如果是3年, 薪资涨300;如果是4年,薪资涨400;其他的涨薪500
DELIMITER //
CREATE PROCEDURE update_salary_by_eid5(IN emp_id INT)
BEGIN
    DECLARE emp_sal DOUBLE;
    DECLARE hire_year DOUBLE;
    SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
    SELECT ROUND(DATEDIFF(CURDATE(),hire_date)/365) INTO hire_year FROM employees
    WHERE employee_id = emp_id;
    CASE hire_year
        WHEN 0 THEN UPDATE employees SET salary=salary+50 WHERE employee_id = emp_id;
        WHEN 1 THEN UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
        WHEN 2 THEN UPDATE employees SET salary=salary+200 WHERE employee_id = emp_id;
        WHEN 3 THEN UPDATE employees SET salary=salary+300 WHERE employee_id = emp_id;
        WHEN 4 THEN UPDATE employees SET salary=salary+400 WHERE employee_id = emp_id;
        ELSE UPDATE employees SET salary=salary+500 WHERE employee_id = emp_id;
    END CASE;
END //
DELIMITER ;

3) 循环结构之LOOP

LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子 句),跳出循环过程

LOOP语句的基本格式如下:

[loop_label:] LOOP
循环执行的语句
END LOOP [loop_label]

其中,loop_label表示LOOP语句的标注名称,该参数可以省略

举例1:使用LOOP语句进行循环操作,id值小于10时将重复执行循环过程

DECLARE id INT DEFAULT 0;
add_loop:LOOP
    SET id = id +1;
    IF id >= 10 THEN LEAVE add_loop;
    END IF;
END LOOP add_loop;

举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程 “update_salary_loop()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨为 原来的1.1倍。直到全公司的平均薪资达到12000结束。并统计循环次数

DELIMITER //
CREATE PROCEDURE update_salary_loop(OUT num INT)
BEGIN
	DECLARE avg_salary DOUBLE;
	DECLARE loop_count INT DEFAULT 0;
	SELECT AVG(salary) INTO avg_salary FROM employees;
	label_loop:LOOP
        IF avg_salary >= 12000 THEN LEAVE label_loop;
        END IF;
        UPDATE employees SET salary = salary * 1.1;
        SET loop_count = loop_count + 1;
        SELECT AVG(salary) INTO avg_salary FROM employees;
    END LOOP label_loop;
    SET num = loop_count;
END //
DELIMITER ;

4) 循环结构之WHILE

WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:

[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];

while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直至循环条件为假,退出循环

  • 举例1:WHILE语句示例,i值小于10时,将重复执行循环过程,代码如下:
DELIMITER //
CREATE PROCEDURE test_while()
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < 10 DO
    	SET i = i + 1;
    END WHILE;
    SELECT i;
END //
DELIMITER ;
#调用
CALL test_while();
  • 举例2:市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程 “update_salary_while()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家降薪,薪资降 为原来的90%。直到全公司的平均薪资达到5000结束。并统计循环次数
DELIMITER //
CREATE PROCEDURE update_salary_while(OUT num INT)
BEGIN
    DECLARE avg_sal DOUBLE ;
    DECLARE while_count INT DEFAULT 0;
    SELECT AVG(salary) INTO avg_sal FROM employees;
    WHILE avg_sal > 5000 DO
        UPDATE employees SET salary = salary * 0.9;
        SET while_count = while_count + 1;
        SELECT AVG(salary) INTO avg_sal FROM employees;
    END WHILE;
    SET num = while_count;
END //
DELIMITER ;

5) 循环结构之REPEAT

REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止

REPEAT语句的基本格式如下:

[repeat_label:] REPEAT
循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]

repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至 expr_condition为真

举例1:

DELIMITER //
CREATE PROCEDURE test_repeat()
BEGIN
    DECLARE i INT DEFAULT 0;
    REPEAT
    	SET i = i + 1;
    UNTIL i >= 10
    END REPEAT;
    SELECT i;
END //
DELIMITER ;

举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程 “update_salary_repeat()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨 为原来的1.15倍。直到全公司的平均薪资达到13000结束。并统计循环次数

DELIMITER //
CREATE PROCEDURE update_salary_repeat(OUT num INT)
BEGIN
    DECLARE avg_sal DOUBLE ;
    DECLARE repeat_count INT DEFAULT 0;
    SELECT AVG(salary) INTO avg_sal FROM employees;
    REPEAT
    	UPDATE employees SET salary = salary * 1.15;
    	SET repeat_count = repeat_count + 1;
    	SELECT AVG(salary) INTO avg_sal FROM employees;
    UNTIL avg_sal >= 13000
    END REPEAT;
    SET num = repeat_count;
END //
DELIMITER ;

对比三种循环结构:

  1. 这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称

  2. LOOP:一般用于实现简单的"死"循环 WHILE:先判断后执行

  3. REPEAT:先执行后判断,无条件至少执行一次

6) 跳转语句之LEAVE语句

LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出 程序体的操作。可以把 LEAVE 理解为 break

基本格式如下:

LEAVE 标记名

其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用

举例1:创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN…END加标记名,并在 BEGIN…END中使用IF语句判断num参数的值

如果num<=0,则使用LEAVE语句退出BEGIN…END; 如果num=1,则查询“employees”表的平均薪资; 如果num=2,则查询“employees”表的最低薪资; 如果num>2,则查询“employees”表的最高薪资

IF语句结束后查询“employees”表的总人数

DELIMITER //
CREATE PROCEDURE leave_begin(IN num INT)
    begin_label: BEGIN
        IF num<=0
        	THEN LEAVE begin_label;
        ELSEIF num=1
        	THEN SELECT AVG(salary) FROM employees;
        ELSEIF num=2
        	THEN SELECT MIN(salary) FROM employees;
        ELSE
        	SELECT MAX(salary) FROM employees;
        END IF;
        SELECT COUNT(*) FROM employees;
    END //
DELIMITER ;

举例2: 当市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程“leave_while()”,声明 OUT参数num,输出循环次数,存储过程中使用WHILE循环给大家降低薪资为原来薪资的90%,直到全公司的平均薪资小于等于10000,并统计循环次数

DELIMITER //
CREATE PROCEDURE leave_while(OUT num INT)
BEGIN
    DECLARE avg_sal DOUBLE;#记录平均工资
    DECLARE while_count INT DEFAULT 0; #记录循环次数
    SELECT AVG(salary) INTO avg_sal FROM employees; #① 初始化条件
    while_label:WHILE TRUE DO #② 循环条件
    #③ 循环体
    IF avg_sal <= 10000 THEN
    LEAVE while_label;
    END IF;
    UPDATE employees SET salary = salary * 0.9;
    SET while_count = while_count + 1;
    #④ 迭代条件
    SELECT AVG(salary) INTO avg_sal FROM employees;
    END WHILE;
    #赋值
    SET num = while_count;
END //
DELIMITER ;

7) 跳转语句之ITERATE语句

ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序转到语句段开头处。可以把 ITERATE 理解为 continue,意思为“再次循环“

语句基本格式如下:

ITERATE label

label参数表示循环的标志。ITERATE语句必须跟在循环标志前面

举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作

  • 如果num < 10,则继续执行循环;
  • 如果num > 15,则退出循环结构;
DELIMITER //
CREATE PROCEDURE test_iterate()
BEGIN
    DECLARE num INT DEFAULT 0;
    my_loop:LOOP
    	SET num = num + 1;
        IF num < 10
        	THEN ITERATE my_loop;
        ELSEIF num > 15
        	THEN LEAVE my_loop;
        END IF;
        SELECT 'MySQL';
    END LOOP my_loop;
END //
DELIMITER ;

4. 游标

1) 游标(光标)概念

可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录, 但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一 条记录 ,并对记录的数据进行处理,可以用游标。

游标,提供了一种灵活的操作方式,能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过程开发的能力

在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,可以通过操作游标来对数据行进行操作

MySQL中游标可以在存储过程和函数中使用

2) 使用游标步骤

游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明

使用游标,一般需要经历四个步骤。不同的 DBMS中,使用游标的语法可能略有不同

第一步,声明游标

在MySQL中,使用DECLARE关键字来声明游标,其语法的基本形式如下:

DECLARE cursor_name CURSOR FOR select_statement;

这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是 Oracle 或者 PostgreSQL,需要写成:

DECLARE cursor_name CURSOR IS select_statement;

要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里 select_statement 代表的是 SELECT 语句,返回一个用于创建游标的结果集

比如:

DECLARE cur_emp CURSOR FOR
SELECT employee_id,salary FROM employees;

第二步,打开游标

当定义好游标之后,如果想要使用游标,必须先打开游标

OPEN cursor_name

当定义好游标之后,如果想要使用游标,必须先打开游标。打开游标的时候 SELECT 语句的查询结果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备

OPEN cur_emp;

第三步,使用游标(从游标中取得数据)

语法如下:

FETCH cursor_name INTO var_name [, var_name] ...

作用是使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游标指针指到下一行。如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可

注意:var_name必须在声明游标之前就定义好

FETCH cur_emp INTO emp_id, emp_sal ;

注意:游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致,否则,在存储过程执行的时候,MySQL会提示错误

第四步,关闭游标

CLOSE cursor_name

使用完游标后需要关闭掉该游标。因为游标会占用系统资源,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。关闭游标的操作,会释放游标占用的系统资源

关闭游标之后,不能再检索查询结果中的数据行,如果需要检索只能再次打开游标

CLOSE cur_emp;

3) 举例

创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,DOUBLE类型;声明 OUT参数total_count,INT类型。函数的功能可以实现累加薪资最高的几个员工的薪资值,直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count

DELIMITER //
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE, OUT total_count INT)
BEGIN
	DECLARE sum_salary DOUBLE DEFAULT 0; # 记录累加的总工资
	DECLARE cursor_salary DOUBLE DEFAULT 0; # 记录某一个工资值
	DECLARE emp_count INT DEFAULT 0; # 记录循环个数
	# 定义游标
	DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
	# 打开游标
	OPEN emp_cursor;
	
	REPEAT
		# 使用游标(从游标中获取数据)
		FETCH emp_cursor INTO cursor_salary;
		SET sum_salary = sum_salary + cursor_salary;
		SET emp_count = emp_count + 1;
		UNTIL sum_salary >= limit_total_salary
	END REPEAT;
	set total_count = emp_count;
	# 关闭游标
	CLOSE emp_cursor;
END //
DELIMITER;

4) 小结

游标是 MySQL 的一个重要的功能,为 逐条读取结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁

但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁 ,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理

建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率

补充:MySQL 8.0的新特性—全局变量的持久化

在MySQL数据库中,全局变量可以通过SET GLOBAL语句来设置。例如,设置服务器语句超时的限制,可以通过设置系统变量max_execution_time来实现:

SET GLOBAL MAX_EXECUTION_TIME=2000;

使用SET GLOBAL语句设置的变量值只会临时生效 。 数据库重启后,服务器又会从MySQL配置文件中读取 变量的默认值 MySQL 8.0版本新增了 SET PERSIST 命令。例如,设置服务器的最大连接数为1000:

SET PERSIST global max_connections = 1000;

MySQL会将该命令的配置保存到数据目录下的 mysqld-auto.cnf 文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件

十五、触发器

例如有 2 个或者多个相互关联的表,如 商品信息 和 库存信息 分 别存放在 2 个不同的数据表中,在添加一条新商品记录的时候,为了保证数据的完整性,必须同时在库存表中添加一条库存记录,此时必须把这两个关联的操作步骤写到程序里面,而且要用事务包裹起来,确保这两个操作成为一个原子操作 ,要么全部执行,要么全部不执行。要是遇到特殊情况,可能还需要对数据进行手动维护,这样就很容易忘记其中的一步 ,导致数据缺失

这个时候,可以使用触发器。创建一个触发器,让商品信息数据的插入操作自动触发库存数据的插入操作。这样一来,就不用担心因为忘记添加库存数据而导致的数据缺失了

1. 触发器概述

触发器是由 事件来触发 某个操作,这些事件包括 INSERT 、 UPDATE 、 DELETE 事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会自动激发触发器执行相应的操作

当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来实现

2. 触发器的创建

1) 语法

CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块

说明:

  • 表名 :表示触发器监控的对象。
  • BEFORE|AFTER :表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发
  • INSERT|UPDATE|DELETE :表示触发的事件
    • INSERT 表示插入记录时触发;
    • UPDATE 表示更新记录时触发;
    • DELETE 表示删除记录时触发。
  • 触发器执行的语句块 :可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块

2) 举例

举例1:

  1. 创建数据表:
CREATE TABLE test_trigger (
id INT PRIMARY KEY AUTO_INCREMENT,
t_note VARCHAR(30)
);

CREATE TABLE test_trigger_log (
id INT PRIMARY KEY AUTO_INCREMENT,
t_log VARCHAR(30)
);
  1. 创建触发器:创建名称为before_insert的触发器,向test_trigger数据表插入数据之前,向 test_trigger_log数据表中插入before_insert的日志信息
DELIMITER //
CREATE TRIGGER before_insert
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN
    INSERT INTO test_trigger_log (t_log)
    VALUES('before_insert');
END //
DELIMITER ;
  1. 向test_trigger数据表中插入数据
INSERT INTO test_trigger (t_note) VALUES ('测试 BEFORE INSERT 触发器');
  1. 查看test_trigger_log数据表中的数据
mysql> SELECT * FROM test_trigger_log;
+----+---------------+
| id | t_log |
+----+---------------+
| 1 | before_insert |
+----+---------------+
1 row in set (0.00 sec)

举例2:

定义触发器“salary_check_trigger”,基于员工表“employees”的INSERT事件,在INSERT之前检查 将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报sqlstate_value为’HY000’的错 误,从而使得添加失败

DELIMITER //
CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
    DECLARE mgrsalary DOUBLE;
    SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id;
    IF NEW.salary > mgrsalary THEN
    	SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
    END IF;
END //
DELIMITER ;

上面触发器声明过程中的NEW关键字代表INSERT添加语句的新记录

3. 查看、删除触发器

1) 查看触发器

查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等

方式1:查看当前数据库的所有触发器的定义

SHOW TRIGGERS;

方式2:查看当前数据库中某个触发器的定义

SHOW CREATE TRIGGER 触发器名

方式3:从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息

SELECT * FROM information_schema.TRIGGERS;

2) 删除触发器

触发器也是数据库对象,删除触发器也用DROP语句,语法格式如下:

DROP TRIGGER IF EXISTS 触发器名称;

4. 触发器的优缺点

1) 优点

1、触发器可以确保数据的完整性

假设用 进货单头表 (demo.importhead)来保存进货单的总体信息,包括进货单编号、供货商编号、仓库编号、总计进货数量、总计进货金额和验收日期

listnumber (进货单编号) supplierid (进货商编号) stockid (参库编号) quantity (总计数量) importvalue (总计金额) confirmationdate (验收日期)

用进货单明细表 (demo.importdetails)来保存进货商品的明细,包括进货单编号、商品编号、进货数 量、进货价格和进货金额

listnumber (进货单编号) itemnumber (商品编号) quantity (进货数量) importprice (进货价格) importvalue (进货金额)

每当录入、删除和修改一条进货单明细数据的时候,进货单明细表里的数据就会发生变动。这个时候,在进货单头表中的总计数量和总计金额就必须重新计算,否则,进货单头表中的总计数量和总计金额就不等于进货单明细表中数量合计和金额合计了,导致数据不一致

为了解决这个问题,可以使用触发器,规定每当进货单明细表有数据插入、修改和删除的操作时,自动触发2步操作:

  1. 重新计算进货单明细表中的数量合计和金额合计
  2. 用第一步中计算出来的值更新进货单头表中的合计数量与合计金额

此时,进货单头表中的合计数量与合计金额的值,就始终与进货单明细表中计算出来的合计数量与合计金额的值相同,数据就是一致的,不会出现数据不一致的现象

2、触发器可以帮助我们记录操作日志

利用触发器,可以具体记录什么时间发生了什么

3、触发器还可以用在操作数据前,对数据进行合法性检查

比如,超市进货的时候,需要库管录入进货价格。但是,人为操作很容易犯错误,比如说在录入数量的时候,把条形码扫进去了;录入金额的时候,看串了行,录入的价格远超售价,导致账面上的巨亏…… 这些都可以通过触发器,在实际插入或者更新操作之前,对相应的数据进行检查,及时提示错误,防止错误数据进入系统

2) 缺点

1、触发器最大的一个问题就是可读性差

因为触发器存储在数据库中,并且由事件驱动,意味着触发器有可能不受应用层的控制,这对系统维护是非常有挑战的

2、相关数据的变更,可能会导致触发器出错

特别是数据表结构的变更,都可能会导致触发器出错,进而影响数据操作的正常运行。这些都会由于触发器本身的隐蔽性,影响到应用中错误原因排查的效率

3) 注意

如果在子表中定义了外键约束,并且外键指定了ON UPDATE/DELETE CASCADE/SET NULL子句,此时修改父表被引用的键值或删除父表被引用的记录行时,会引起子表的修改和删除操作,此时基于子表的UPDATE和DELETE语句定义的触发器不会被激活。

例如:基于子表员工表(t_employee)的DELETE语句定义了触发器t1,而子表的部门编号(did)字段定义了外键约束引用了父表部门表(t_department)的主键列部门编号(did),并且该外键加了“ON DELETE SET NULL”子句,那么如果此时删除父表部门表(t_department)在子表员工表(t_employee) 有匹配记录的部门记录时,会引起子表员工表(t_employee)匹配记录的部门编号(did)修改为NULL, mysql> update demo.membermaster set memberdeposit=20 where memberid = 2; ERROR 1054 (42S22): Unknown column ‘aa’ in ‘field list’ 但是此时不会激活触发器t1。只有直接对子表员工表(t_employee)执行DELETE语句时才会激活触发器 t1

十六、MySQL8其他新特性

1. MySQL8新特性概述

MySQL 8版本在功能上做了显著的改进与增强,开发者对MySQL的源代码进行了重构,最突出的一点是多MySQL Optimizer优化器进行了改进。不仅在速度上得到了改善,还为用户带来了更好的性能和更棒的体验

1) MySQL8.0 新增特性

  1. 更简便的NoSQL支持 NoSQL泛指非关系型数据库和数据存储。随着互联网平台的规模飞速发展,传统的关系型数据库已经越来越不能满足需求。从5.6版本开始,MySQL就开始支持简单的NoSQL存储功能。MySQL 8对这一功能做了优化,以更灵活的方式实现NoSQL功能,不再依赖模式(schema)

  2. 更好的索引在查询中,正确地使用索引可以提高查询的效率。MySQL 8中新增了隐藏索引降序索引 。隐藏索引可以用来测试去掉索引对查询性能的影响。在查询中混合存在多列索引时,使用降序索引 可以提高查询的性能

  3. 更完善的JSON支持 MySQL从5.7开始支持原生JSON数据的存储,MySQL 8对这一功能做了优化,增加了聚合函数 JSON_ARRAYAGG() 和 JSON_OBJECTAGG(),将参数聚合为JSON数组或对象,新增了行内 操作符 ->>,是列路径运算符 ->的增强,对JSON排序做了提升,并优化了JSON的更新操作

  4. 安全和账户管理 MySQL 8中新增了 caching_sha2_password 授权插件、角色、密码历史记录和FIPS模式支持,这些特性提高了数据库的安全性和性能,使数据库管理员能够更灵活地进行账户管理工作

  5. InnoDB的变化 InnoDB是MySQL默认的存储引擎 ,是事务型数据库的首选引擎,支持事务安全表 (ACID),支持行锁定和外键。在MySQL 8版本中,InnoDB在自增、索引、加密、死锁、共享锁等方面做了大量的改进和优化 ,并且支持原子数据定义语言(DDL),提高了数据安全性,对事务提供更好的支持

  6. 数据字典在之前的MySQL版本中,字典数据都存储在元数据文件和非事务表中。从MySQL 8开始新增了事务数据字典,在这个字典里存储着数据库对象信息,这些数据字典存储在内部事务表中

  7. 原子数据定义语句 MySQL 8开始支持原子数据定义语句(Automic DDL),即原子DDL 。目前,只有 InnoDB存储引擎支持原子DDL。原子数据定义语句(DDL)将与DDL操作相关的数据字典更新、存储引擎 操作、二进制日志写入结合到一个单独的原子事务中,这使得即使服务器崩溃,事务也会提交或回滚。 使用支持原子操作的存储引擎所创建的表,在执行DROP TABLE、CREATE TABLE、ALTER TABLE、 RENAME TABLE、TRUNCATE TABLE、CREATE TABLESPACE、DROP TABLESPACE等操作时,都支持原子操 作,即事务要么完全操作成功,要么失败后回滚,不再进行部分提交。 对于从MySQL 5.7复制到MySQL 8 版本中的语句,可以添加 IF EXISTS 或 IF NOT EXISTS 语句来避免发生错误

  8. 资源管理 MySQL 8开始支持创建和管理资源组,允许将服务器内运行的线程分配给特定的分组,以便 线程根据组内可用资源执行。组属性能够控制组内资源,启用或限制组内资源消耗。数据库管理员能够 根据不同的工作负载适当地更改这些属性。 目前,CPU时间是可控资源,由“虚拟CPU”这个概念来表示,此术语包含CPU的核心数,超线程,硬件线程等等。服务器在启动时确定可用的虚拟CPU数量。拥有 对应权限的数据库管理员可以将这些CPU与资源组关联,并为资源组分配线程。 资源组组件为MySQL中的资源组管理提供了SQL接口。资源组的属性用于定义资源组。MySQL中存在两个默认组,系统组和用户 组,默认的组不能被删除,其属性也不能被更改。对于用户自定义的组,资源组创建时可初始化所有的属性,除去名字和类型,其他属性都可在创建之后进行更改。 在一些平台下,或进行了某些MySQL的配 置时,资源管理的功能将受到限制,甚至不可用。例如,如果安装了线程池插件,或者使用的是macOS 系统,资源管理将处于不可用状态。在FreeBSD和Solaris系统中,资源线程优先级将失效。在Linux系统 中,只有配置了CAP_SYS_NICE属性,资源管理优先级才能发挥作用

  9. 字符集支持 MySQL 8中默认的字符集由 latin1 更改为 utf8mb4 ,并首次增加了日语所特定使用的集 合,utf8mb4_ja_0900_as_cs

  10. 优化器增强 MySQL优化器开始支持隐藏索引和降序索引。隐藏索引不会被优化器使用,验证索引的必 要性时不需要删除索引,先将索引隐藏,如果优化器性能无影响就可以真正地删除索引。降序索引允许 优化器对多个列进行排序,并且允许排序顺序不一致

  11. 公用表表达式 公用表表达式(Common Table Expressions)简称为CTE,MySQL现在支持递归和非递 归两种形式的CTE。CTE通过在SELECT语句或其他特定语句前 使用WITH语句对临时结果集 进行命名

    基础语法如下:

    WITH cte_name (col_name1,col_name2 ...) AS (Subquery)
    SELECT * FROM cte_name;
    

​ Subquery代表子查询,子查询前使用WITH语句将结果集命名为cte_name,在后续的查询中即可使用 cte_name进行查询

  1. 窗口函数 MySQL 8开始支持窗口函数。在之前的版本中已存在的大部分 聚合函数 在MySQL 8中也可以 作为窗口函数来使用

  2. 正则表达式支持 MySQL在8.0.4以后的版本中采用支持Unicode的国际化组件库实现正则表达式操作, 这种方式不仅能提供完全的Unicode支持,而且是多字节安全编码。MySQL增加了REGEXP_LIKE()、 EGEXP_INSTR()、REGEXP_REPLACE()和 REGEXP_SUBSTR()等函数来提升性能。另外,regexp_stack_limit和 regexp_time_limit 系统变量能够通过匹配引擎来控制资源消耗

  3. 内部临时表 TempTable存储引擎取代MEMORY存储引擎成为内部临时表的默认存储引擎 。TempTable存储 引擎为VARCHAR和VARBINARY列提供高效存储。internal_tmp_mem_storage_engine会话变量定义了内部 临时表的存储引擎,可选的值有两个,TempTable和MEMORY,其中TempTable为默认的存储引擎。 temptable_max_ram系统配置项定义了TempTable存储引擎可使用的最大内存数量

  4. 日志记录 在MySQL 8中错误日志子系统由一系列MySQL组件构成。这些组件的构成由系统变量 log_error_services来配置,能够实现日志事件的过滤和写入。 WITH cte_name (col_name1,col_name2 …) AS (Subquery) SELECT * FROM cte_name

  5. 备份锁 新的备份锁允许在线备份期间执行数据操作语句,同时阻止可能造成快照不一致的操作。新 备份锁由 LOCK INSTANCE FOR BACKUP 和 UNLOCK INSTANCE 语法提供支持,执行这些操作需要备份管理 员特权

  6. 增强的MySQL复制 MySQL 8复制支持对 JSON文档 进行部分更新的 二进制日志记录 ,该记录 使用紧凑 的二进制格式 ,从而节省记录完整JSON文档的空间。当使用基于语句的日志记录时,这种紧凑的日志记 录会自动完成,并且可以通过将新的binlog_row_value_options系统变量值设置为PARTIAL_JSON来启用

2. 新特性1:窗口函数

1) 使用窗口函数前后对比

假设有这样一个数据表,它显示了某购物网站在每个城市每个区的销售额:

CREATE TABLE sales(
id INT PRIMARY KEY AUTO_INCREMENT,
city VARCHAR(15),
county VARCHAR(15),
sales_value DECIMAL
);
INSERT INTO sales(city,county,sales_value)
VALUES
('北京','海淀',10.00),
('北京','朝阳',20.00),
('上海','黄埔',30.00),
('上海','长宁',10.00);

查询:

mysql> SELECT * FROM sales;
+----+------+--------+-------------+
| id | city | county | sales_value |
+----+------+--------+-------------+
| 1  | 北京  |  海淀   |      10    |
| 2  | 北京  |  朝阳   |      20    |
| 3  | 上海  |  黄埔   |      30    |
| 4  | 上海  |  长宁   |      10    |
+----+------+--------+-------------+
4 rows in set (0.00 sec)

需求:现在计算这个网站在每个城市的销售总额、在全国的销售总额、每个区的销售额占所在城市销售额中的比率,以及占总销售额中的比率。

  • 如果用分组和聚合函数,就需要分好几步来计算

    • 第一步,计算总销售金额,并存入临时表a:

      CREATE TEMPORARY TABLE a -- 创建临时表
      SELECT SUM(sales_value) AS sales_value -- 计算总计金额
      FROM sales;
      
    • 查看一下临时表a :

      mysql> SELECT * FROM a;
      +-------------+
      | sales_value |
      +-------------+
      | 70 |
      +-------------+
      1 row in set (0.00 sec)
      
    • 第二步,计算每个城市的销售总额并存入临时表b:

      CREATE TEMPORARY TABLE b -- 创建临时表
      SELECT city, SUM(sales_value) AS sales_value -- 计算城市销售合计
      FROM sales
      GROUP BY city;
      
    • 查看临时表b :

      mysql> SELECT * FROM b;
      +------+-------------+
      | city | sales_value |
      +------+-------------+
      | 北京  |     30      |
      | 上海  |     40      |
      +------+-------------+
      2 rows in set (0.00 sec)
      
    • 第三步,计算各区的销售占所在城市的总计金额的比例,和占全部销售总计金额的比例。可以通过下面的连接查询获得需要的结果:

      mysql> SELECT s.city AS 城市,s.county AS,s.sales_value AS 区销售额,
      -> b.sales_value AS 市销售额,s.sales_value/b.sales_value AS 市比率,
      -> a.sales_value AS 总销售额,s.sales_value/a.sales_value AS 总比率
      -> FROM sales s
      -> JOIN b ON (s.city=b.city) -- 连接市统计结果临时表
      -> JOIN a -- 连接总计金额临时表
      -> ORDER BY s.city,s.county;
      +------+------+----------+----------+--------+----------+--------+
      | 城市 || 区销售额 | 市销售额 | 市比率 | 总销售额 | 总比率 |
      +------+------+----------+----------+--------+----------+--------+
      | 上海 | 长宁 | 10 | 40 | 0.2500 | 70 | 0.1429 |
      | 上海 | 黄埔 | 30 | 40 | 0.7500 | 70 | 0.4286 |
      | 北京 | 朝阳 | 20 | 30 | 0.6667 | 70 | 0.2857 |
      | 北京 | 海淀 | 10 | 30 | 0.3333 | 70 | 0.1429 |
      +------+------+----------+----------+--------+----------+--------+
      4 rows in set (0.00 sec)
      
    • 结果显示:市销售金额、市销售占比、总销售金额、总销售占比都计算出来了

  • 同样的查询,如果用窗口函数,可以用下面的代码来实现:

    mysql> SELECT city AS 城市,county AS,sales_value AS 区销售额,
    -> SUM(sales_value) OVER(PARTITION BY city) AS 市销售额, -- 计算市销售额
    -> sales_value/SUM(sales_value) OVER(PARTITION BY city) AS 市比率,
    -> SUM(sales_value) OVER() AS 总销售额, -- 计算总销售额
    -> sales_value/SUM(sales_value) OVER() AS 总比率
    -> FROM sales
    -> ORDER BY city,county;
    +------+------+----------+----------+--------+----------+--------+
    | 城市 || 区销售额 | 市销售额 | 市比率 | 总销售额 | 总比率 |
    +------+------+----------+----------+--------+----------+--------+
    | 上海 | 长宁 | 10 | 40 | 0.2500 | 70 | 0.1429 |
    | 上海 | 黄埔 | 30 | 40 | 0.7500 | 70 | 0.4286 |
    | 北京 | 朝阳 | 20 | 30 | 0.6667 | 70 | 0.2857 |
    | 北京 | 海淀 | 10 | 30 | 0.3333 | 70 | 0.1429 |
    +------+------+----------+-----------+--------+----------+--------+
    4 rows in set (0.00 sec)
    
    • 结果显示:市销售金额、市销售占比、总销售金额、总销售占比都计算出来了

    • 使用窗口函数,只用了一步就完成了查询。而且,由于没有用到临时表,执行的效率也更高了。显然,在这种需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好

2) 窗口函数分类

窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将结果置于每一条数据记录中

窗口函数可以分为 静态窗口函数 和 动态窗口函数

  • 静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同
  • 动态窗口函数的窗口大小会随着记录的不同而变化

窗口函数总体上可以分为序号函数、分布函数、前后函数、首尾函数和其他函数

3) 语法结构

窗口函数的语法结构是:

函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])

或者是:

函数 OVER 窗口名 … WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
  • OVER 关键字指定函数窗口的范围
    • 如果省略后面括号中的内容,则窗口会包含满足WHERE条件的所有记录,窗口函数会基于所有满足WHERE条件的记录进行计算
    • 如果OVER关键字后面的括号不为空,则可以使用如下语法设置窗口
  • 窗口名:为窗口设置一个别名,用来标识窗口
  • PARTITION BY子句:指定窗口函数按照哪些字段进行分组。分组后,窗口函数可以在每个分组中分别执行
  • ORDER BY子句:指定窗口函数按照哪些字段进行排序。执行排序操作使窗口函数按照排序后的数据记录的顺序进行编号
  • FRAME子句:为分区中的某个子集定义规则,可以用来作为滑动窗口使用

4) 分类讲解

创建表:

CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT,
category VARCHAR(15),
NAME VARCHAR(30),
price DECIMAL(10,2),
stock INT,
upper_time DATETIME
);

添加数据:

INSERT INTO goods(category_id,category,NAME,price,stock,upper_time)
VALUES
(1, '女装/女士精品', 'T恤', 39.90, 1000, '2020-11-10 00:00:00'),
(1, '女装/女士精品', '连衣裙', 79.90, 2500, '2020-11-10 00:00:00'),
(1, '女装/女士精品', '卫衣', 89.90, 1500, '2020-11-10 00:00:00'),
(1, '女装/女士精品', '牛仔裤', 89.90, 3500, '2020-11-10 00:00:00'),
(1, '女装/女士精品', '百褶裙', 29.90, 500, '2020-11-10 00:00:00'),
(1, '女装/女士精品', '呢绒外套', 399.90, 1200, '2020-11-10 00:00:00'),
(2, '户外运动', '自行车', 399.90, 1000, '2020-11-10 00:00:00'),
(2, '户外运动', '山地自行车', 1399.90, 2500, '2020-11-10 00:00:00'),
(2, '户外运动', '登山杖', 59.90, 1500, '2020-11-10 00:00:00'),
(2, '户外运动', '骑行装备', 399.90, 3500, '2020-11-10 00:00:00'),
(2, '户外运动', '运动外套', 799.90, 500, '2020-11-10 00:00:00'),
(2, '户外运动', '滑板', 499.90, 1200, '2020-11-10 00:00:00');

下面针对goods表中的数据来验证每个窗口函数的功能

1) 序号函数

1. ROW_NUMBER()函数

ROW_NUMBER()函数能够对数据中的序号进行顺序显示

举例:查询 goods 数据表中每个商品分类下价格降序排列的各个商品信息

mysql> SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num, id, category_id, category, NAME, price, stock
FROM goods;
+---------+----+-------------+---------------+------------+---------+-------+
| row_num | id | category_id |    category   |     NAME   |  price  | stock |
+---------+----+-------------+---------------+------------+---------+-------+
|    1    |  6 |     1       |  女装/女士精品  | 呢绒外套     | 399.90  | 1200  |
|    2    |  3 |     1       |  女装/女士精品  | 卫衣        | 89.90   | 1500  |
|    3    |  4 |     1       |  女装/女士精品  | 牛仔裤       | 89.90   | 3500  |
|    4    |  2 |     1       |  女装/女士精品  | 连衣裙       | 79.90   | 2500  |
|    5    |  1 |     1       |  女装/女士精品  | T恤         | 39.90   | 1000  |
|    6    |  5 |     1       |  女装/女士精品  | 百褶裙       | 29.90   | 500   |
|    1    |  8 |     2       |     户外运动   | 山地自行车    | 1399.90 | 2500  |
|    2    | 11 |     2       |     户外运动   | 运动外套      | 799.90  | 500  |
|    3    | 12 |     2       |     户外运动   | 滑板         | 499.90  | 1200  |
|    4    |  7 |     2       |     户外运动   | 自行车       | 399.90  | 1000  |
|    5    | 10 |     2       |     户外运动   | 骑行装备     | 399.90  | 3500  |
|    6    |  9 |     2       |     户外运动   | 登山杖       | 59.90   | 1500  |
+---------+----+-------------+---------------+------------+---------+-------+
12 rows in set (0.00 sec)

举例:查询 goods 数据表中每个商品分类下价格最高的3种商品信息

mysql> SELECT *
-> FROM (
-> SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num,
-> id, category_id, category, NAME, price, stock
-> FROM goods) t
-> WHERE row_num <= 3;
+---------+----+-------------+---------------+------------+---------+-------+
| row_num | id | category_id |     category  |      NAME  |  price  | stock |
+---------+----+-------------+---------------+------------+---------+-------+
|     1   |  6 |      1      | 女装/女士精品   | 呢绒外套     | 399.90  | 1200  |
|     2   |  3 |      1      | 女装/女士精品   | 卫衣        | 89.90   | 1500  |
|     3   |  4 |      1      | 女装/女士精品   | 牛仔裤      | 89.90    | 3500 |
|     1   |  8 |      2      | 户外运动       | 山地自行车   | 1399.90  | 2500 |
|     2   | 11 |      2      | 户外运动       | 运动外套     | 799.90  | 500   |
|     3   | 12 |      2      | 户外运动       | 滑板        | 499.90   | 1200 |
+---------+----+-------------+---------------+------------+----------+-------+
6 rows in set (0.00 sec)

在名称为“女装/女士精品”的商品类别中,有两款商品的价格为89.90元,分别是卫衣和牛仔裤。两款商品 的序号都应该为2,而不是一个为2,另一个为3。此时,可以使用RANK()函数和DENSE_RANK()函数解决

2.RANK()函数

使用RANK()函数能够对序号进行并列排序,并且会跳过重复的序号,比如序号为1、1、3

举例:使用RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息

mysql> SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
-> id, category_id, category, NAME, price, stock
-> FROM goods;
+---------+----+-------------+---------------+------------+---------+-------+
| row_num | id | category_id | category      | NAME       | price   | stock |
+---------+----+-------------+---------------+------------+---------+-------+
|     1   | 6  |     1       | 女装/女士精品   | 呢绒外套     | 399.90  | 1200  |
|     2   | 3  |     1       | 女装/女士精品   | 卫衣        | 89.90   | 1500  |
|     2   | 4  |     1       | 女装/女士精品   | 牛仔裤      | 89.90   | 3500   |
|     4   | 2  |     1       | 女装/女士精品   | 连衣裙      | 79.90   | 2500   |
|     5   | 1  |     1       | 女装/女士精品   | T恤        | 39.90   | 1000   |
|     6   | 5  |     1       | 女装/女士精品   | 百褶裙      | 29.90   | 500    |
|     1   | 8  |     2       | 户外运动       | 山地自行车   | 1399.90 | 2500   |
|     2   | 11 |     2       | 户外运动       | 运动外套     | 799.90  | 500   |
|     3   | 12 |     2       | 户外运动       | 滑板        | 499.90  | 1200   |
|     4   | 7  |     2       | 户外运动       | 自行车      | 399.90   | 1000  |
|     4   | 10 |     2       | 户外运动       | 骑行装备    | 399.90   | 3500  |
|     6   | 9  |     2       | 户外运动       | 登山杖      | 59.90   | 1500   |
+---------+----+-------------+---------------+------------+---------+-------+
12 rows in set (0.00 sec)

3.DENSE_RANK()函数

DENSE_RANK()函数对序号进行并列排序,并且不会跳过重复的序号,比如序号为1、1、2。 举例:使用DENSE_RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息

mysql> SELECT DENSE_RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num,
-> id, category_id, category, NAME, price, stock
-> FROM goods;
+---------+----+-------------+---------------+------------+---------+-------+
| row_num | id | category_id | category      | NAME       | price   | stock |
+---------+----+-------------+---------------+------------+---------+-------+
|    1    | 6  |      1      | 女装/女士精品   |     呢绒外套 | 399.90 | 1200   |
|    2    | 3  |      1      | 女装/女士精品   |     卫衣    | 89.90  | 1500   |
|    2    | 4  |      1      | 女装/女士精品   |     牛仔裤  | 89.90   | 3500  |
|    3    | 2  |      1      | 女装/女士精品   |     连衣裙  | 79.90   | 2500  |
|    4    | 1  |      1      | 女装/女士精品   |     T恤    | 39.90   | 1000  |
|    5    | 5  |      1      | 女装/女士精品   |     百褶裙  | 29.90   | 500   |
|    1    | 8  |      2      | 户外运动       |    山地自行车| 1399.90 | 2500 |
|    2    | 11 |      2      | 户外运动       |    运动外套  | 799.90 | 500    |
|    3    | 12 |      2      | 户外运动       |    滑板     | 499.90 | 1200   |
|    4    | 7  |      2      | 户外运动       |    自行车    | 399.90 | 1000   |
|    4    | 10 |      2      | 户外运动       |    骑行装备  | 399.90 | 3500   |
|    5    | 9  |      2      | 户外运动       |    登山杖    | 59.90 | 1500   |
+---------+----+-------------+---------------+------------+---------+-------+
12 rows in set (0.00 sec)

2) 分布函数

1.PERCENT_RANK()函数

PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算

(rank - 1) / (rows - 1)

其中,rank的值为使用RANK()函数产生的序号,rows的值为当前窗口的总记录数

举例:计算 goods 数据表中名称为“女装/女士精品”的类别下的商品的PERCENT_RANK值

#写法一:
SELECT RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS r,
PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS pr,
id, category_id, category, NAME, price, stock
FROM goods
WHERE category_id = 1;
#写法二:
mysql> SELECT RANK() OVER w AS r,
-> PERCENT_RANK() OVER w AS pr,
-> id, category_id, category, NAME, price, stock
-> FROM goods
-> WHERE category_id = 1 WINDOW w AS (PARTITION BY category_id ORDER BY price
DESC);
+---+-----+----+-------------+---------------+----------+--------+-------+
| r | pr  | id | category_id | category      | NAME     | price  | stock |
+---+-----+----+-------------+---------------+----------+--------+-------+
| 1 | 0   | 6  |          1  | 女装/女士精品   |   呢绒外套 | 399.90 | 1200 |
| 2 | 0.2 | 3  |          1  | 女装/女士精品   |   卫衣    | 89.90 | 1500 |
| 2 | 0.2 | 4  |          1  | 女装/女士精品   |   牛仔裤  | 89.90 | 3500 |
| 4 | 0.6 | 2  |          1  | 女装/女士精品   |   连衣裙  | 79.90 | 2500 |
| 5 | 0.8 | 1  |          1  | 女装/女士精品   |   T恤    | 39.90 | 1000 |
| 6 | 1   | 5  |          1  | 女装/女士精品   |   百褶裙  | 29.90 | 500 |
+---+-----+----+-------------+---------------+----------+--------+-------+
6 rows in set (0.00 sec)

2.CUME_DIST()函数

CUME_DIST()函数主要用于查询小于或等于某个值的比例

举例:查询goods数据表中小于或等于当前价格的比例

mysql> SELECT CUME_DIST() OVER(PARTITION BY category_id ORDER BY price ASC) AS cd,
-> id, category, NAME, price
-> FROM goods;
+---------------------+----+---------------+------------+---------+
|                cd   | id | category      | NAME       | price   |
+---------------------+----+---------------+------------+---------+
| 0.16666666666666666 | 5  | 女装/女士精品   | 百褶裙      | 29.90 |
| 0.3333333333333333  | 1  | 女装/女士精品   | T恤        | 39.90 |
| 0.5                 | 2  | 女装/女士精品   | 连衣裙      | 79.90 |
| 0.8333333333333334  | 3  | 女装/女士精品   | 卫衣       | 89.90 |
| 0.8333333333333334  | 4  | 女装/女士精品   | 牛仔裤     | 89.90 |
| 1                   | 6  | 女装/女士精品   | 呢绒外套    | 399.90 |
| 0.16666666666666666 | 9  | 户外运动       | 登山杖      | 59.90 |
| 0.5                 | 7  | 户外运动       | 自行车      | 399.90 |
| 0.5                 | 10 | 户外运动       | 骑行装备     | 399.90 |
| 0.6666666666666666  | 12 | 户外运动       | 滑板        | 499.90 |
| 0.8333333333333334  | 11 | 户外运动       | 运动外套     | 799.90 |
| 1                   | 8  | 户外运动       | 山地自行车   | 1399.90 |
+---------------------+----+---------------+------------+---------+
12 rows in set (0.00 sec)

3) 前后函数

1.LAG(expr,n)函数

LAG(expr,n)函数返回当前行的前n行的expr的值

举例:查询goods数据表中前一个商品价格与当前商品价格的差值

mysql> SELECT id, category, NAME, price, pre_price, price - pre_price AS diff_price
-> FROM (
-> SELECT id, category, NAME, price,LAG(price,1) OVER w AS pre_price
-> FROM goods
-> WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;
+----+---------------+------------+---------+-----------+------------+
| id | category | NAME | price | pre_price | diff_price |
+----+---------------+------------+---------+-----------+------------+
| 5 | 女装/女士精品 | 百褶裙 | 29.90 | NULL | NULL |
| 1 | 女装/女士精品 | T恤 | 39.90 | 29.90 | 10.00 |
| 2 | 女装/女士精品 | 连衣裙 | 79.90 | 39.90 | 40.00 |
| 3 | 女装/女士精品 | 卫衣 | 89.90 | 79.90 | 10.00 |
| 4 | 女装/女士精品 | 牛仔裤 | 89.90 | 89.90 | 0.00 |
| 6 | 女装/女士精品 | 呢绒外套 | 399.90 | 89.90 | 310.00 |
| 9 | 户外运动 | 登山杖 | 59.90 | NULL | NULL |
| 7 | 户外运动 | 自行车 | 399.90 | 59.90 | 340.00 |
| 10 | 户外运动 | 骑行装备 | 399.90 | 399.90 | 0.00 |
| 12 | 户外运动 | 滑板 | 499.90 | 399.90 | 100.00 |
| 11 | 户外运动 | 运动外套 | 799.90 | 499.90 | 300.00 |
| 8 | 户外运动 | 山地自行车 | 1399.90 | 799.90 | 600.00 |
+----+---------------+------------+---------+-----------+------------+
12 rows in set (0.00 sec)

2.LEAD(expr,n)函数

LEAD(expr,n)函数返回当前行的后n行的expr的值

举例:查询goods数据表中后一个商品价格与当前商品价格的差值

mysql> SELECT id, category, NAME, behind_price, price,behind_price - price AS
diff_price
-> FROM(
-> SELECT id, category, NAME, price,LEAD(price, 1) OVER w AS behind_price
-> FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;
+----+---------------+------------+--------------+---------+------------+
| id | category      | NAME       | behind_price | price   | diff_price |
+----+---------------+------------+--------------+---------+------------+
| 5  | 女装/女士精品   | 百褶裙       | 39.90       | 29.90 | 10.00 |
| 1  | 女装/女士精品   | T恤         | 79.90       | 39.90 | 40.00 |
| 2  | 女装/女士精品   | 连衣裙      | 89.90        | 79.90 | 10.00 |
| 3  | 女装/女士精品   | 卫衣        | 89.90       | 89.90 | 0.00 |
| 4  | 女装/女士精品   | 牛仔裤       | 399.90     | 89.90 | 310.00 |
| 6  | 女装/女士精品   | 呢绒外套     | NULL       | 399.90 | NULL |
| 9  | 户外运动       | 登山杖       | 399.90    | 59.90 | 340.00 |
| 7  | 户外运动       | 自行车       | 399.90    | 399.90 | 0.00 |
| 10 | 户外运动       | 骑行装备     | 499.90     | 399.90 | 100.00 |
| 12 | 户外运动       | 滑板         | 799.90    | 499.90 | 300.00 |
| 11 | 户外运动       | 运动外套     | 1399.90    | 799.90 | 600.00 |
| 8  | 户外运动       | 山地自行车   | NULL       | 1399.90 | NULL |
+----+---------------+------------+--------------+---------+------------+
12 rows in set (0.00 sec)

4) 首尾函数

1.FIRST_VALUE(expr)函数

FIRST_VALUE(expr)函数返回第一个expr的值

举例:按照价格排序,查询第1个商品的价格信息

mysql> SELECT id, category, NAME, price, stock,FIRST_VALUE(price) OVER w AS
first_price
-> FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
+----+---------------+------------+---------+-------+-------------+
| id | category      | NAME | price | stock | first_price |
+----+---------------+------------+---------+-------+-------------+
| 5  | 女装/女士精品   | 百褶裙 | 29.90 | 500 | 29.90 |
| 1  | 女装/女士精品   | T恤 | 39.90 | 1000 | 29.90 |
| 2  | 女装/女士精品   | 连衣裙 | 79.90 | 2500 | 29.90 |
| 3  | 女装/女士精品   | 卫衣 | 89.90 | 1500 | 29.90 |
| 4  | 女装/女士精品   | 牛仔裤 | 89.90 | 3500 | 29.90 |
| 6  | 女装/女士精品   | 呢绒外套 | 399.90 | 1200 | 29.90 |
| 9  | 户外运动       | 登山杖 | 59.90 | 1500 | 59.90 |
| 7  | 户外运动       | 自行车 | 399.90 | 1000 | 59.90 |
| 10 | 户外运动       | 骑行装备 | 399.90 | 3500 | 59.90 |
| 12 | 户外运动       | 滑板 | 499.90 | 1200 | 59.90 |
| 11 | 户外运动       | 运动外套 | 799.90 | 500 | 59.90 |
| 8  | 户外运动       | 山地自行车 | 1399.90 | 2500 | 59.90 |
+----+---------------+------------+---------+-------+-------------+
12 rows in set (0.00 sec)

LAST_VALUE(expr)函数

LAST_VALUE(expr)函数返回最后一个expr的值

举例:按照价格排序,查询最后一个商品的价格信息

mysql> SELECT id, category, NAME, price, stock,LAST_VALUE(price) OVER w AS last_price
-> FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
+----+---------------+------------+---------+-------+------------+
| id | category      | NAME | price | stock | last_price |
+----+---------------+------------+---------+-------+------------+
| 5  | 女装/女士精品   | 百褶裙 | 29.90 | 500 | 29.90 |
| 1  | 女装/女士精品   | T恤 | 39.90 | 1000 | 39.90 |
| 2  | 女装/女士精品   | 连衣裙 | 79.90 | 2500 | 79.90 |
| 3  | 女装/女士精品   | 卫衣 | 89.90 | 1500 | 89.90 |
| 4  | 女装/女士精品   | 牛仔裤 | 89.90 | 3500 | 89.90 |
| 6  | 女装/女士精品   | 呢绒外套 | 399.90 | 1200 | 399.90 |
| 9  | 户外运动       | 登山杖 | 59.90 | 1500 | 59.90 |
| 7  | 户外运动       | 自行车 | 399.90 | 1000 | 399.90 |
| 10 | 户外运动       | 骑行装备 | 399.90 | 3500 | 399.90 |
| 12 | 户外运动       | 滑板 | 499.90 | 1200 | 499.90 |
| 11 | 户外运动       | 运动外套 | 799.90 | 500 | 799.90 |
| 8  | 户外运动       | 山地自行车 | 1399.90 | 2500 | 1399.90 |
+----+---------------+------------+---------+-------+------------+
12 rows in set (0.00 sec)

5) 其他函数

1.NTH_VALUE(expr,n)函数

NTH_VALUE(expr,n)函数返回第n个expr的值。 举例:查询goods数据表中排名第2和第3的价格信息

mysql> SELECT id, category, NAME, price,NTH_VALUE(price,2) OVER w AS second_price,
-> NTH_VALUE(price,3) OVER w AS third_price
-> FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
+----+---------------+------------+---------+--------------+-------------+
| id | category      | NAME       | price   | second_price | third_price |
+----+---------------+------------+---------+--------------+-------------+
| 5  | 女装/女士精品   | 百褶裙 | 29.90 | NULL | NULL |
| 1  | 女装/女士精品   | T恤 | 39.90 | 39.90 | NULL |
| 2  | 女装/女士精品   | 连衣裙 | 79.90 | 39.90 | 79.90 |
| 3  | 女装/女士精品   | 卫衣 | 89.90 | 39.90 | 79.90 |
| 4  | 女装/女士精品   | 牛仔裤 | 89.90 | 39.90 | 79.90 |
| 6  | 女装/女士精品   | 呢绒外套 | 399.90 | 39.90 | 79.90 |
| 9  | 户外运动       | 登山杖 | 59.90 | NULL | NULL |
| 7  | 户外运动       | 自行车 | 399.90 | 399.90 | 399.90 |
| 10 | 户外运动       | 骑行装备 | 399.90 | 399.90 | 399.90 |
| 12 | 户外运动       | 滑板 | 499.90 | 399.90 | 399.90 |
| 11 | 户外运动       | 运动外套 | 799.90 | 399.90 | 399.90 |
| 8  | 户外运动       | 山地自行车 | 1399.90 | 399.90 | 399.90 |
+----+---------------+------------+---------+--------------+-------------+
12 rows in set (0.00 sec)

2.NTILE(n)函数

NTILE(n)函数将分区中的有序数据分为n个桶,记录桶编号

举例:将goods表中的商品按照价格分为3组

mysql> SELECT NTILE(3) OVER w AS nt,id, category, NAME, price
-> FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
+----+----+---------------+------------+---------+
| nt | id | category      | NAME       | price |
+----+----+---------------+------------+---------+
| 1  | 5  | 女装/女士精品   | 百褶裙 | 29.90 |
| 1  | 1  | 女装/女士精品   | T恤 | 39.90 |
| 2  | 2  | 女装/女士精品   | 连衣裙 | 79.90 |
| 2  | 3  | 女装/女士精品   | 卫衣 | 89.90 |
| 3  | 4  | 女装/女士精品   | 牛仔裤 | 89.90 |
| 3  | 6  | 女装/女士精品   | 呢绒外套 | 399.90 |
| 1  | 9  | 户外运动       | 登山杖 | 59.90 |
| 1  | 7  | 户外运动       | 自行车 | 399.90 |
| 2  | 10 | 户外运动       | 骑行装备 | 399.90 |
| 2  | 12 | 户外运动       | 滑板 | 499.90 |
| 3  | 11 | 户外运动       | 运动外套 | 799.90 |
| 3  | 8  | 户外运动       | 山地自行车 | 1399.90 |
+----+----+---------------+------------+---------+
12 rows in set (0.00 sec)

5) 小结

窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少原表中的行数

3. 新特性2:公用表表达式

公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。CTE是一个命名的临时结 果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的, CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询

依据语法结构和执行方式的不同,公用表表达式分为 普通公用表表达式 和 递归公用表表达式 2 种

1) 普通公用表表达式

普通公用表表达式的语法结构是:

WITH CTE名称
AS (子查询)
SELECT|DELETE|UPDATE 语句;

普通公用表表达式类似于子查询,不过跟子查询不同的是,它可以被多次引用,而且可以被其他的普通公用表表达式所引用

举例:查询员工所在的部门的详细信息

mysql> SELECT * FROM departments
-> WHERE department_id IN (
-> SELECT DISTINCT department_id
-> FROM employees
-> );
+---------------+------------------+------------+-------------+
| department_id | department_name  | manager_id | location_id |
+---------------+------------------+------------+-------------+
|     10        | Administration   | 200        | 1700        |
|     20        | Marketing        | 201        | 1800        |
|     30        | Purchasing       | 114        | 1700        |
|     40        | Human Resources  | 203        | 2400        |
|     50        | Shipping         | 121        | 1500        |
|     60        | IT               | 103        | 1400        |
|     70        | Public Relations | 204        | 2700        |
|     80        | Sales            | 145        | 2500        |
|     90        | Executive        | 100        | 1700        |
|     100       | Finance          | 108        | 1700        |
|     110       | Accounting       | 205        | 1700        |
+---------------+------------------+------------+-------------+
11 rows in set (0.00 sec)

这个查询也可以用普通公用表表达式的方式完成:

mysql> WITH emp_dept_id
-> AS (SELECT DISTINCT department_id FROM employees)
-> SELECT *
-> FROM departments d JOIN emp_dept_id e
-> ON d.department_id = e.department_id;
+---------------+------------------+------------+-------------+---------------+
| department_id | department_name  | manager_id | location_id | department_id |
+---------------+------------------+------------+-------------+---------------+
|      90       | Executive        | 100        | 1700        | 90            |
|      60       | IT               | 103        | 1400        | 60            |
|      100      | Finance          | 108        | 1700        | 100           |
|      30       | Purchasing       | 114        | 1700        | 30            |
|      50       | Shipping         | 121        | 1500        | 50            |
|      80       | Sales            | 145        | 2500        | 80            |
|      10       | Administration   | 200        | 1700        | 10            |
|      20       | Marketing        | 201        | 1800        | 20            |
|      40       | Human Resources  | 203        | 2400        | 40            |
|      70       | Public Relations | 204        | 2700        | 70            |
|      110      | Accounting       | 205        | 1700        | 110           |
+---------------+------------------+------------+-------------+---------------+
11 rows in set (0.00 sec)

例子说明,公用表表达式可以起到子查询的作用。以后如果遇到需要使用子查询的场景,可以在查询之前,先定义公用表表达式,然后在查询中用它来代替子查询。而且,跟子查询相比,公用表表达式有一个优点,就是定义过公用表表达式之后的查询,可以像一个表一样多次引用公用表表达式,而子查询则不能

2) 递归公用表表达式

递归公用表表达式也是一种公用表表达式,只不过,除了普通公用表表达式的特点以外,它还有自己的特点,就是可以调用自己。它的语法结构是:

WITH RECURSIVE
CTE名称 AS (子查询)
SELECT|DELETE|UPDATE 语句;

递归公用表表达式由 2 部分组成,分别是种子查询和递归查询,中间通过关键字 UNION [ALL]进行连接。 这里的种子查询,意思就是获得递归的初始值。这个查询只会运行一次,以创建初始数据集,之后递归 查询会一直执行,直到没有任何新的查询数据产生,递归返回

案例:针对于常用的employees表,包含employee_id,last_name和manager_id三个字段。如果a是b 的管理者,可以把b叫做a的下属,如果同时b又是c的管理者,那么c就是b的下属,是a的下下属

用查询语句列出所有具有下下属身份的人员信息

如果用之前学过的知识来解决

  • 第一步,先找出初代管理者,就是不以任何别人为管理者的人,把结果存入临时表
  • 第二步,找出所有以初代管理者为管理者的人,得到一个下属集,把结果存入临时表
  • 第三步,找出所有以下属为管理者的人,得到一个下下属集,把结果存入临时表
  • 第四步,找出所有以下下属为管理者的人,得到一个结果集

如果第四步的结果集为空,则计算结束,第三步的结果集就是需要的下下属集了,否则就必须继续进行第四步,一直到结果集为空为止。比如上面的这个数据表,就需要到第五步,才能得到空结果集。 而且,最后还要进行第六步:把第三步和第四步的结果集合并,这样才能最终获得需要的结果集

如果用递归公用表表达式

  • 用递归公用表表达式中的种子查询,找出初代管理者。字段 n 表示代次,初始值为 1,表示是第一代管理者
  • 用递归公用表表达式中的递归查询,查出以这个递归公用表表达式中的人为管理者的人,并且代次 的值加 1。直到没有人以这个递归公用表表达式中的人为管理者了,递归返回
  • 在最后的查询中,选出所有代次大于等于 3 的人,他们肯定是第三代及以上代次的下属了,也就是下下属了。这样就得到了需要的结果集

代码实现:

WITH RECURSIVE cte
AS
(
SELECT employee_id,last_name,manager_id,1 AS n FROM employees WHERE employee_id = 100
-- 种子查询,找到第一代领导
UNION ALL
SELECT a.employee_id,a.last_name,a.manager_id,n+1 FROM employees AS a JOIN cte
ON (a.manager_id = cte.employee_id) -- 递归查询,找出以递归公用表表达式的人为领导的人
)
SELECT employee_id,last_name FROM cte WHERE n >= 3;

递归公用表表达式对于查询一个有共同的根节点的树形结构数据,非常有用。它可以不受层级的限制,轻松查出所有节点的数据

3) 小结

公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根 节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询

你可能感兴趣的:(mysql,mysql)