从这里开始学习除表以外的数据库对象
回过头来,我们 创建数据库,创建数据表,创建视图,创建存储过程,创建触发器 都是在创建一个具体类型的对象
create database xxx
create table xxx
create view xxx
create procedure xxx
create trigger xxx
我们也会声明不同类型的变量,声明局部变量,声明条件,声明处理程序,声明游标
declare xx int default 0
declare xx condition for yy
declare [continue, exit, undo] handler for yy
declare xx cursor for yy
十四. 视图
函数,约束居然都是对象,万物皆对象
为什么使用视图view
视图:一个或者多个数据表里的数据的逻辑显示,视图并不存储数据
怎么解释数据的逻辑显示??
在组原学习内存知识的时候,提到过物理地址和逻辑地址,这两者是映射关系,中间需要一个算法进行值转换
在实际的生产中,有这样一个需求:对同一张表,不同的人只能访问有限的字段!!!
怎么实现这个需求呢????
创建包含相关字段的子表吗??那要做数据更改又怎么办呢?子表和原表又没有关系
创建一个包含相关字段的视图。他在底层实现了访问视图的记录就是在访问原表的记录,但她隐藏了这一点
(1)视图是一种虚拟表
(2)本身是 不具有数据 的
(3)视图建立在已有表的基础上, 视图赖以建立的这些表称为基表
可以将视图视为存储起来的select语句???很抽象
优点:简化查询,控制权限
创建视图view
CREATE DATABASE dbtest14;
USE dbtest14;
CREATE TABLE emps
AS
SELECT *
FROM atguigudb.`employees`;
CREATE TABLE depts
AS
SELECT *
FROM atguigudb.`departments`;
在 dbtest14数据库造了两张表,employees和departments表
针对单表
#2.1 针对于单表
#情况1:视图中的字段与基表的字段有对应关系
CREATE VIEW vu_emp1
AS
SELECT employee_id,last_name,salary
FROM emps;
SELECT * FROM vu_emp1;
#确定视图中字段名的方式1:
CREATE VIEW vu_emp2
AS
SELECT employee_id emp_id,last_name lname,salary #查询语句中字段的别名会作为视图中字段的名称出现
FROM emps
WHERE salary > 8000;
#确定视图中字段名的方式2:
CREATE VIEW vu_emp3(emp_id,NAME,monthly_sal) #小括号内字段个数与SELECT中字段个数相同
AS
SELECT employee_id,last_name,salary
FROM emps
WHERE salary > 8000;
SELECT * FROM vu_emp3;
#情况2:视图中的字段在基表中可能没有对应的字段
CREATE VIEW vu_emp_sal
AS
SELECT department_id,AVG(salary) avg_sal
FROM emps
WHERE department_id IS NOT NULL
GROUP BY department_id;
SELECT * FROM vu_emp_sal;
针对多表
#2.2 针对于多表
CREATE VIEW vu_emp_dept
AS
SELECT e.employee_id,e.department_id,d.department_name
FROM emps e JOIN depts d
ON e.`department_id` = d.`department_id`;
SELECT * FROM vu_emp_dept;
我们经常需要输出某个格式的内容,比如我们想输出员工姓名和对应的部门名,对应格式为emp_name(department_name),就可以使用视图来完成数据格式化的操作
#利用视图对数据进行格式化
CREATE VIEW vu_emp_dept1
AS
SELECT CONCAT(e.last_name,'(',d.department_name,')') emp_info
FROM emps e JOIN depts d
ON e.`department_id` = d.`department_id`;
SELECT * FROM vu_emp_dept1;
乍一看concat()懵了一下,其实就是返回一个字符串
视图的嵌套
#2.3 基于视图创建视图
CREATE VIEW vu_emp4
AS
SELECT employee_id,last_name
FROM vu_emp1;
SELECT * FROM vu_emp4;
查看视图view
不是看view里的数据,而是看数据库里边有多少视图
#3. 查看视图
# 语法1:查看数据库的表对象、视图对象
SHOW TABLES;
#语法2:查看视图的结构(数据表结构)
DESCRIBE vu_emp1;
#语法3:查看视图的属性信息
SHOW TABLE STATUS LIKE 'vu_emp1'\G;
#语法4:查看视图的详细定义信息
SHOW CREATE VIEW vu_emp1;
更新数据(增,改,删)
#4."更新"视图中的数据
#4.1 一般情况,可以更新视图的数据
SELECT * FROM vu_emp1;
SELECT employee_id,last_name,salary
FROM emps;
#更新视图的数据,会导致基表中数据的修改
UPDATE vu_emp1
SET salary = 20000
WHERE employee_id = 101;
#同理,更新基表中的数据,也会导致视图中的数据的修改
UPDATE emps
SET salary = 10000
WHERE employee_id = 101;
#删除视图中的数据,也会导致表中的数据的删除
DELETE FROM vu_emp1
WHERE employee_id = 101;
SELECT employee_id,last_name,salary
FROM emps
WHERE employee_id = 101;
#4.2 不能更新视图中的数据,基表中没有这个字段
SELECT * FROM vu_emp_sal;
#更新失败
UPDATE vu_emp_sal
SET avg_sal = 5000
WHERE department_id = 30;
#删除失败
DELETE FROM vu_emp_sal
WHERE department_id = 30;
虽然可以更新视图数据,但总的来说,视图作为 虚拟表 ,主要用于 方便查询 ,不建议更新视图的数据。对视图数据的更改,都是通过对实际数据表里数据的操作来完成的
管理视图
修改视图
#5. 修改视图
DESC vu_emp1;
#方式1 or replace如果存在视图,则替换
CREATE OR REPLACE VIEW vu_emp1
AS
SELECT employee_id,last_name,salary,email
FROM emps
WHERE salary > 7000;
#方式2
ALTER VIEW vu_emp1
AS
SELECT employee_id,last_name,salary,email,hire_date
FROM emps;
删除视图
#6. 删除视图
SHOW TABLES;
DROP VIEW vu_emp4;
DROP VIEW IF EXISTS vu_emp2,vu_emp3;
优点
数据安全
MySQL将用户对数据的 访问限制 在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用户不必直接查询或操作数据表。这也可以理解为视图具有 隔离性 。视图相当于在用户和实际的数据表之间加了一层虚拟表
适应灵活多变的需求
当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较大,可以使用视图来减少改动的工作量。这种方式在实际工作中使用得比较多
不动数据库中的表了,在上面覆盖一层各种各样的视图,对外也只暴露视图
能够分解复杂的查询逻辑
将视图作为计算图的节点
缺点
维护成本高
实际数据表的结构变更了,我们就需要及时对相关的视图进行相应的维护。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复杂, 可读性不好 ,容易变成系统的潜在隐患
十五. 存储过程与函数(自定义)
存储过程和函数能够将复杂的SQL逻辑封装在一起
应用程序无须关注存储过程和函数内部复杂的SQL逻辑,而只需要简单地调用存储过程和函数即可
存储过程的英文是 Stored Procedure 。它的思想很简单,就是一组经过 预先编译 的 SQL 语句的封装
与视图view和函数的对比
视图:被视为虚拟表,虽然也将视图视作存储起来的select语句,但是我们一般不通过视图进行增删改,而只是进行查询
函数:mysql中的函数一定有返回值,存储过程可以没有返回值
分类
存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:
(1)没有参数(无参数无返回)
(2)仅仅带 IN 类型(有参数无返回)
(3)仅仅带 OUT 类型(无参数有返回)
(4)既带 IN 又带 OUT(有参数有返回)
(5)带 INOUT(有参数有返回)
注意:IN、OUT、INOUT 都可以在一个存储过程中带多个
IN :当前参数为输入参数,也就是表示入参;存储过程只是读取这个参数的值。如果没有定义参数种类, 默认就是 IN ,表示输入参数。
OUT :当前参数为输出参数,也就是表示出参;执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。
INOUT :当前参数既可以为输入参数,也可以为输出参数
delimiter设置新的语句结束符号
MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用DELIMITER改变存储过程的结束符
DELIMITER $
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
sql语句1;
sql语句2;
END $
创建存储过程
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END
修饰符 返回类型 方法名(参数类型 参数名,...){
方法体;
}
十六. 变量、流程控制与游标
变量
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据
在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量
系统变量
启动MySQL服务,生成MySQL服务实例
MySQL将为MySQL服务器内存中的系统变量赋值
这些系统变量定义了当前MySQL服务实例的属性、特
征。这些系统变量的值要么是 编译MySQL时参数 的默认值,要么是 配置文件 (例如my.ini等)中的参数值
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话
会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制
分类
全局系统变量global
会话系统变量session
查看系统变量
#1.2 查看系统变量
#查询全局系统变量
SHOW GLOBAL VARIABLES; #sql8.0是617个
#查询会话系统变量
SHOW SESSION VARIABLES; #640个
SHOW VARIABLES; #默认查询的是会话系统变量,因为是从客户端发出的指令
#查询部分系统变量
SHOW GLOBAL VARIABLES LIKE 'admin_%';
SHOW VARIABLES LIKE 'character_%';
查看指定系统变量
作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头,其中“@@global”仅用于标记全局系统变量,“@@session”仅用于标记会话系统变量。“@@”首先标记会话系统变量,如果会话系统变量不存在,则标记全局系统变量
#1.3 查看指定系统变量
SELECT @@global.max_connections;
SELECT @@global.character_set_client;
#错误:
SELECT @@global.pseudo_thread_id;
#错误:
SELECT @@session.max_connections;
SELECT @@session.character_set_client;
SELECT @@session.pseudo_thread_id;
SELECT @@character_set_client; #先查询会话系统变量,再查询全局系统变量
之所以先查询会话系统变量,我猜想这种系统变量数量更多
修改系统变量的值
#1.4 修改系统变量的值
#全局系统变量:
#方式1:
SET @@global.max_connections = 161;
#方式2:
SET GLOBAL max_connections = 171;
#针对于当前的数据库实例是有效的,一旦重启mysql服务,就失效了。
#会话系统变量:
#方式1:
SET @@session.character_set_client = 'gbk';
#方式2:
SET SESSION character_set_client = 'gbk';
#针对于当前会话是有效的,一旦结束会话,重新建立起新的会话,就失效了。
用户变量(自定义变量)
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以 一个“@” 开头
会话用户变量:作用域和会话变量一样,只对 当前连接 会话有效。
局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在 存储过程和函数 中使用
会话用户变量
=号和:=的区别
1)=
只有在set和update时才是和:=一样,赋值的作用,其它都是等于的作用。
2):=
不只在set和update时时赋值的作用,在select也是赋值的作用。
赋值
会话用户变量的赋值有两种方法
SELECT last_name INTO emp_name FROM employees WHERE employee_id = 101;
select emp_name := last_name FROM employees WHERE employee_id = 101;
#1.6 会话用户变量
/*
① 变量的声明和赋值:
#方式1:“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
#方式2:“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
② 使用
SELECT @变量名
*/
#准备工作
CREATE DATABASE dbtest16;
USE dbtest16;
CREATE TABLE employees
AS
SELECT * FROM atguigudb.`employees`;
CREATE TABLE departments
AS
SELECT * FROM atguigudb.`departments`;
SELECT * FROM employees;
SELECT * FROM departments;
#测试:
#方式1:
SET @m1 = 1;
SET @m2 := 2;
SET @sum := @m1 + @m2;
SELECT @sum;
#方式2:
SELECT @count := COUNT(*) FROM employees;
SELECT @count;
SELECT AVG(salary) INTO @avg_sal FROM employees;
SELECT @avg_sal;
局部变量
赋值
局部变量的赋值不像会话用户变量有两种方法,只有一种
SELECT last_name INTO emp_name FROM employees WHERE employee_id = 101;
局部变量不能像会话用户变量那样有这种赋值方式
select emp_name := last_name FROM employees WHERE employee_id = 101;
#1.7 局部变量
/*
1、局部变量必须满足:
① 使用DECLARE声明,用以区分会话用户变量
② 声明并使用在BEGIN ... END 中 (使用在存储过程、函数中)
③ DECLARE的方式声明的局部变量必须声明在BEGIN中的首行的位置。
2、声明格式:
DECLARE 变量名 类型 [default 值]; # 如果没有DEFAULT子句,初始值为NULL
3、赋值:
方式1:
SET 变量名=值;
SET 变量名:=值;
方式2:
SELECT 字段名或表达式 INTO 变量名 FROM 表;
4、使用
SELECT 局部变量名;
*/
#举例:
DELIMITER //
CREATE PROCEDURE test_var()
BEGIN
#1、声明局部变量
DECLARE a INT DEFAULT 0;
DECLARE b INT ;
#DECLARE a,b INT DEFAULT 0;
DECLARE emp_name VARCHAR(25);
#2、赋值
SET a = 1;
SET b := 2;
SELECT last_name INTO emp_name FROM employees WHERE employee_id = 101;
# 局部变量不能像会话用户变量那样有这种赋值方式
#select emp_name := last_name FROM employees WHERE employee_id = 101;
#3、使用
SELECT a,b,emp_name;
END //
DELIMITER ;
#调用存储过程
CALL test_var();
#举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary
DELIMITER //
CREATE PROCEDURE test_pro()
BEGIN
#声明
DECLARE emp_name VARCHAR(25);
DECLARE sal DOUBLE(10,2) DEFAULT 0.0;
#赋值
SELECT last_name,salary INTO emp_name,sal
FROM employees
WHERE employee_id = 102;
#使用
SELECT emp_name,sal;
END //
DELIMITER ;
#调用存储过程
CALL test_pro();
SELECT last_name,salary FROM employees
WHERE employee_id = 102;
#举例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)
#方式1:使用会话用户变量
SET @v1 = 10;
SET @v2 := 20;
SET @result := @v1 + @v2;
#查看
SELECT @result;
#方式2:使用局部变量
DELIMITER //
CREATE PROCEDURE add_value()
BEGIN
#声明
DECLARE value1,value2,sum_val INT;
#赋值
SET value1 = 10;
SET value2 := 100;
SET sum_val = value1 + value2;
#使用
SELECT sum_val;
END //
DELIMITER ;
#调用存储过程
CALL add_value();
#举例3:创建存储过程“different_salary”查询某员工和他领导的薪资差距,并用IN参数emp_id接收员工id,
#用OUT参数dif_salary输出薪资差距结果。
DELIMITER //
CREATE PROCEDURE different_salary(IN emp_id INT,OUT dif_salary DOUBLE)
BEGIN
#分析:查询出emp_id员工的工资;查询出emp_id员工的管理者的id;查询管理者id的工资;计算两个工资的差值
#声明变量
DECLARE emp_sal DOUBLE DEFAULT 0.0; #记录员工的工资
DECLARE mgr_sal DOUBLE DEFAULT 0.0; #记录管理者的工资
DECLARE mgr_id INT DEFAULT 0; #记录管理者的id
#赋值
SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
SELECT manager_id INTO mgr_id FROM employees WHERE employee_id = emp_id;
SELECT salary INTO mgr_sal FROM employees WHERE employee_id = mgr_id;
SET dif_salary = mgr_sal - emp_sal;
END //
DELIMITER ;
#调用存储过程
#声明会话用户变量作为存储过程的参数
SET @emp_id := 103;
SET @dif_sal := 0;
CALL different_salary(@emp_id,@dif_sal);
SELECT @dif_sal;
SELECT * FROM employees;
定义条件和处理程序(异常)
自带的错误码过于抽象
定义条件 是事先定义程序执行过程中可能遇到的问题
处理程序 定义了在遇到问题时应当采取的处理方式
类似于java的异常处理??我们一般处理的是编译时异常
运行时异常作为bug,在上线前就要解决
案例分析
#2.1 错误演示:
#错误代码: 1364
#Field 'email' doesn't have a default value
INSERT INTO employees(last_name)
VALUES('Tom');
DESC employees;
#错误演示:
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 ;
#调用存储过程
#错误代码: 1048
#Column 'email' cannot be null:sqlyog返回的结果
CALL UpdateDataNoCondition();
SELECT @x;
定义条件condition for
事先定义程序执行过程中可能遇到的问题
定义条件就是给MySQL中的错误码命名
它将一个 错误名字 和 指定的错误条件 关联起来
对于condition for:
It’s a necessary condition for all of us to enter the university,对我们所有人来说,这是上大学的一个必要条件
MySQL_error_code 和 sqlstate_value 都可以表示MySQL的错误:
MySQL_error_code是数值类型错误代码。
sqlstate_value是长度为5的字符串类型错误代码
#2.2 定义条件
#格式:DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
#举例1:定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型
#是“ERROR 1048 (23000)”对应。
#方式1:使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
#方式2:使用sqlstate_value
DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';
#举例2:定义"ERROR 1148(42000)"错误,名称为command_not_allowed。
#方式1:使用MySQL_error_code
DECLARE command_not_allowed CONDITION FOR 1148;
#方式2:使用sqlstate_value
DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
定义处理程序
handler:操作者,顾问,驯兽员
DECLARE 处理方式 HANDLER: 将处理方式声明为操作者
处理方式
处理方式有3个取值:CONTINUE、EXIT、UNDO。
(1)CONTINUE :表示遇到错误不处理,继续执行。
(2)EXIT :表示遇到错误马上退出。
(3)UNDO :表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作
错误类型(即条件)
有如下取值:
(1)SQLSTATE ‘字符串错误码’ :表示长度为5的sqlstate_value类型的错误代码;
(2)MySQL_error_code :匹配数值类型错误代码;
(3)错误名称 :表示DECLARE … CONDITION定义的错误条件名称。
(4)SQLWARNING :匹配所有以01开头的SQLSTATE错误代码;
(5)NOT FOUND :匹配所有以02开头的SQLSTATE错误代码;
(6)SQLEXCEPTION :匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码
#2.3 定义处理程序
#格式:DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
#举例:
#方法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';
案例
#2.4 案例的处理
#在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为1048时,执行
#CONTINUE操作,并且将@proc_value的值设置为-1
DROP PROCEDURE UpdateDataNoCondition;
#重新定义存储过程,体现错误的处理程序
DELIMITER //
CREATE PROCEDURE UpdateDataNoCondition()
BEGIN
#声明处理程序
#处理方式1:
DECLARE CONTINUE HANDLER FOR 1048 SET @prc_value = -1;
#处理方式2:
#DECLARE CONTINUE HANDLER FOR sqlstate '23000' SET @prc_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 ;
#调用存储过程:
CALL UpdateDataNoCondition();
#查看变量:
SELECT @x,@prc_value;
案例二:
#2.5 再举一个例子:
#创建一个名称为“InsertDataWithCondition”的存储过程
#在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到#sqlstate_value值为23000时,执行EXIT操作,并且将#@proc_value的值设置为-1
#① 准备工作
CREATE TABLE departments
AS
SELECT * FROM atguigudb.`departments`;
DESC departments;
#管理表,加上一个表级约束,给department_id字段加唯一性约束
ALTER TABLE departments
ADD CONSTRAINT uk_dept_name UNIQUE(department_id);
#② 定义存储过程:
DELIMITER //
CREATE PROCEDURE InsertDataWithCondition()
BEGIN
SET @x = 1;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 2;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 3;
END //
DELIMITER ;
#③ 调用
CALL InsertDataWithCondition();
SELECT @x; #2
#④ 删除此存储过程
DROP PROCEDURE IF EXISTS InsertDataWithCondition;
#⑤ 重新定义存储过程(考虑到错误的处理程序)
DELIMITER //
CREATE PROCEDURE InsertDataWithCondition()
BEGIN
#处理程序
#方式1:
#declare exit handler for 1062 set @pro_value = -1;
#方式2:
#declare exit handler for sqlstate '23000' set @pro_value = -1;
#方式3:
#定义条件
DECLARE duplicate_entry CONDITION FOR 1062;
DECLARE EXIT HANDLER FOR duplicate_entry SET @pro_value = -1;
SET @x = 1;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 2;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 3;
END //
DELIMITER ;
#调用
CALL InsertDataWithCondition();
SELECT @x,@pro_value;
流程控制
之前只是在单行函数那里讲了4个流程控制函数
当时就觉得这种流程控制是不是太弱了
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
(1)条件判断语句 :IF 语句和 CASE 语句
(2)循环语句 :LOOP、WHILE 和 REPEAT 语句
(3)跳转语句 :ITERATE 和 LEAVE 语句
分支-if
#3.1 分支结构之 IF
#举例1
DELIMITER //
CREATE PROCEDURE test_if()
BEGIN
#情况1:
#声明局部变量
#declare stu_name varchar(15);
# 结构化的if语句
#if stu_name is null
# then select 'stu_name is null';
#end if;
#情况2:二选一
#declare email varchar(25) default 'aaa';
#if email is null
# then select 'email is null';
#else
# select 'email is not null';
#end if;
#情况3:多选一
DECLARE age INT DEFAULT 20;
IF age > 40
THEN SELECT '中老年';
ELSEIF age > 18
THEN SELECT '青壮年';
ELSEIF age > 8
THEN SELECT '青少年';
ELSE
SELECT '婴幼儿';
END IF;
END //
DELIMITER ;
#调用
CALL test_if();
# 更改了存储程序的内容之后,先删掉,再重新执行存储程序
# 然后call 存储程序
DROP PROCEDURE test_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_sal DOUBLE; #记录员工的工资
DECLARE hire_year DOUBLE; #记录员工入职公司的年头
# 局部变量赋值
# 简单地可以用set直接赋值
# 这里显然是通过查询结果来赋值
SELECT salary INTO emp_sal 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_sal < 8000 AND hire_year >= 5
THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
END IF;
END //
DELIMITER ;
#调用存储过程
CALL update_salary_by_eid1(104);
# 验证
SELECT DATEDIFF(CURDATE(),hire_date)/365, employee_id,salary
FROM employees
WHERE salary < 8000 AND DATEDIFF(CURDATE(),hire_date)/365 >= 5;
DROP PROCEDURE update_salary_by_eid1;
#举例3:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。
#判断该员工薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid2(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 DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year FROM employees WHERE employee_id = emp_id;
#判断
IF emp_sal < 9000 AND hire_year >= 5
THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
ELSE
UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
END IF;
END //
DELIMITER ;
#调用
CALL update_salary_by_eid2(103);
CALL update_salary_by_eid2(104);
SELECT * FROM employees
WHERE employee_id IN (103,104);
#举例4:声明存储过程“update_salary_by_eid3”,定义IN参数emp_id,输入员工编号。
#判断该员工薪资如果低于9000元,就更新薪资为9000元;薪资如果大于等于9000元且
#低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)
BEGIN
#声明变量
DECLARE emp_sal DOUBLE; #记录员工工资
DECLARE bonus DOUBLE; #记录员工的奖金率
#赋值
SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
#判断
IF emp_sal < 9000
THEN UPDATE employees SET salary = 9000 WHERE employee_id = emp_id;
ELSEIF 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 IF;
END //
DELIMITER ;
#调用
CALL update_salary_by_eid3(102);
CALL update_salary_by_eid3(103);
CALL update_salary_by_eid3(104);
SELECT *
FROM employees
WHERE employee_id IN (102,103,104);
分支-case when then
当时就提到有两种语法结构,一种类似于switch case,一种类似于多重if
##3.2 分支结构之case
#举例1:基本使用
DELIMITER //
CREATE PROCEDURE test_case()
BEGIN
#演示1:case ... when ...then ...
/*
declare var int default 2;
case var
when 1 then select 'var = 1';
when 2 then select 'var = 2';
when 3 then select 'var = 3';
else select 'other value';
end case;
*/
#演示2:case when ... then ....
DECLARE var1 INT DEFAULT 10;
CASE
WHEN var1 >= 100 THEN SELECT '三位数';
WHEN var1 >= 10 THEN SELECT '两位数';
ELSE SELECT '个数位';
END CASE;
END //
DELIMITER ;
#调用
CALL test_case();
DROP PROCEDURE test_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 DOUBLE; #记录员工的奖金率
#局部变量的赋值
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 ;
#调用
CALL update_salary_by_eid4(103);
CALL update_salary_by_eid4(104);
CALL update_salary_by_eid4(105);
SELECT *
FROM employees
WHERE employee_id IN (103,104,105);
#举例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 hire_year INT; #记录员工入职公司的总时间(单位:年)
#赋值
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 ;
#调用
CALL update_salary_by_eid5(101);
SELECT *
FROM employees
DROP PROCEDURE update_salary_by_eid5;
循环
凡是循环结构,一定具备4个要素:
- 初始化条件
- 循环条件
- 循环体
- 迭代条件
循环-loop leave
LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子句),跳出循环过程
loop_label表示LOOP语句的标注名称,该参数可以省略
#4.1 循环结构之LOOP
/*
[loop_label:] LOOP
循环执行的语句
END LOOP [loop_label]
loop_label表示LOOP语句的标注名称,该参数可以省略
*/
#举例1:
DELIMITER //
CREATE PROCEDURE test_loop()
BEGIN
#声明局部变量
DECLARE num INT DEFAULT 1;
loop_label:LOOP
#重新赋值
SET num = num + 1;
#可以考虑某个代码程序反复执行。(略)
IF num >= 10 THEN LEAVE loop_label;
END IF;
END LOOP loop_label;
#查看num
SELECT num;
END //
DELIMITER ;
#调用
CALL test_loop();
#举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。
#声明存储过程“update_salary_loop()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家涨薪,薪资涨为原来的1.1倍。直到全公司的平
#均薪资达到12000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_loop(OUT num INT)
BEGIN
#声明变量
DECLARE avg_sal DOUBLE ; #记录员工的平均工资
DECLARE loop_count INT DEFAULT 0;#记录循环的次数
#① 初始化条件
#获取员工的平均工资
SELECT AVG(salary) INTO avg_sal FROM employees;
loop_lab:LOOP
#② 循环条件
#结束循环的条件
IF avg_sal >= 12000
THEN LEAVE loop_lab;
END IF;
#③ 循环体
#如果低于12000,更新员工的工资。所有员工涨薪
UPDATE employees SET salary = salary * 1.1;
#④ 迭代条件
#更新avg_sal变量的值
SELECT AVG(salary) INTO avg_sal FROM employees;
#记录循环次数
SET loop_count = loop_count + 1;
END LOOP loop_lab;
#给num赋值
SET num = loop_count;
END //
DELIMITER ;
SELECT AVG(salary) FROM employees;
CALL update_salary_loop(@num);
SELECT @num;
循环-while do
#4.2 循环结构之WHILE
/*
[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];
*/
#举例1:
DELIMITER //
CREATE PROCEDURE test_while()
BEGIN
#初始化条件
DECLARE num INT DEFAULT 1;
#循环条件
WHILE num <= 10 DO
#循环体(略)
#迭代条件
SET num = num + 1;
END WHILE;
#查询
SELECT num;
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;
#给num赋值
SET num = while_count;
END //
DELIMITER ;
#调用
CALL update_salary_while(@num);
SELECT @num;
SELECT AVG(salary) FROM employees;
循环-repeat until
until不要加分号,就好比是前面的while num > 10 do
UNTIL num >= 10
类似于do while,先执行一次,再到循环条件
#4.3 循环结构之REPEAT
/*
[repeat_label:] REPEAT
循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
*/
#举例1:
DELIMITER //
CREATE PROCEDURE test_repeat()
BEGIN
#声明变量
DECLARE num INT DEFAULT 1;
REPEAT
SET num = num + 1;
# until不要加分号,就好比是前面的while num > 10 do
UNTIL num >= 10
END REPEAT;
#查看
SELECT num;
END //
DELIMITER ;
#调用
CALL test_repeat();
#举例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;
#给num赋值
SET num = repeat_count;
END //
DELIMITER ;
#调用
CALL update_salary_repeat(@num);
SELECT @num;
SELECT AVG(salary) FROM employees;
关键字leave-跳转(break)
前面的循环用到了 loop leave
可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break
java循环是被动跳转,超出循环条件之后自动break
在mysql必须要主动跳转,明确声明end,leave这些,感觉很像前端代码
#5.1 LEAVE的使用
/*
**举例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
# 退出beigin-end这个结构
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 ;
#调用
CALL leave_begin(1);
#举例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 ;
#调用
CALL leave_while(@num);
SELECT @num;
SELECT AVG(salary) FROM employees;
关键字iterate-继续(continue)
你可以把 ITERATE 理解为 continue,意思为“再次循环
#5.2 ITERATE的使用
/*
举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
- 如果num < 10,则继续执行循环;
- 如果num > 15,则退出循环结构;
*/
DELIMITER //
CREATE PROCEDURE test_iterate()
BEGIN
DECLARE num INT DEFAULT 0;
loop_label:LOOP
#赋值
SET num = num + 1;
IF num < 10
THEN ITERATE loop_label;
ELSEIF num > 15
THEN LEAVE loop_label;
# 可以没有else,和其他高级编程语言一样
END IF;
SELECT '尚硅谷:让天下没有难学的技术';
END LOOP;
END //
DELIMITER ;
CALL test_iterate();
SELECT * FROM employees;
游标(光标)cursor
有这样一个需求:在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是 随意定位到某一条记录 ,并对记录的数据进行处理。where,having更多的适用于过滤出目标数据,而不是精确定位某一条记录
游标:让我们能够对结果集中的每一条记录进行定位
游标让 SQL 这种面向集合的语言有了面向过程开发的能力
游标使用的步骤:
① 声明游标
游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明
② 打开游标
③ 使用游标(从游标中获取数据)
fetch into: fetch 拿回来
④ 关闭游标
#6. 游标的使用
/*
游标使用的步骤:
① 声明游标
② 打开游标
③ 使用游标(从游标中获取数据)
④ 关闭游标
*/
#举例:创建存储过程“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_sal DOUBLE DEFAULT 0.0; #记录累加的工资总额
DECLARE emp_sal DOUBLE; #记录每一个员工的工资
DECLARE emp_count INT DEFAULT 0;#记录累加的人数
#1.声明游标
DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
#2.打开游标
OPEN emp_cursor;
REPEAT
#3.使用游标
FETCH emp_cursor INTO emp_sal;
# emp_sal是一个局部变量,不是字段
# 每轮循环游标就给他赋一个新值
# 游标感觉就是一个数组,每次从中取出一个数据
SET sum_sal = sum_sal + emp_sal;
SET emp_count = emp_count + 1;
UNTIL sum_sal >= limit_total_salary
END REPEAT;
SET total_count = emp_count;
#4.关闭游标
CLOSE emp_cursor;
END //
DELIMITER ;
#调用
CALL get_count_by_limit_total_salary(200000,@total_count);
SELECT @total_count;
游标的应用场景
游标是 MySQL 的一个重要的功能,为 逐条读取 结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。
但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行 加锁 ,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率
mysql8.0新特性-全局变量的持久化
之前提到了8.0的另一个持久化特性,自增变量的持久化
8.0将自增主键的计数器持久化到 重做日志 中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值
SET GLOBAL MAX_EXECUTION_TIME=2000;
SET PERSIST global max_connections = 1000;
persist:坚持,持续存在
之前,使用SET GLOBAL语句设置的变量值只会 临时生效 。 数据库重启 后,服务器又会从MySQL配置文件中读取
变量的默认值
MySQL 8.0版本新增了 SET PERSIST 命令。MySQL会将该命令的配置保存到数据目录下的 mysqld-auto.cnf 文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件