MySQL基础(续)

目录

十一、视图

1.常见的数据库对象

2.视图概述

3.创建视图

4.查看视图

5.更新视图

6.修改、删除视图

7.总结

十五、存储过程与函数

1.存储过程概述

2.创建存储过程与调用

3.存储函数概述

4.创建存储函数与调用

5.对比存储过程与存储函数

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

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

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

1.变量

2.定义条件与处理程序

3.流程控制

3.1分支结构之IF

3.2分支结构之CASE

3.3循环结构之LOOP

3.4循环结构之WHILE

3.5循环结构之REPEAT

3.6跳转语句之LEAVE语句

3.7跳转语句之ITERATE

4.游标

4.1什么是游标

4.2使用游标的步骤

十七、触发器

1.触发器概述

2.触发器的创建

3.查看和删除触发器

4.触发器的优缺点


十一、视图

1.常见的数据库对象

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

2.视图概述

为什么使用视图 ?

视图一方面可以帮我们使用表的一部分而不是所有的表,另一方面也可以针对不同的用户制定不同的查询视图。比如,针对一个公司的销售人员,我们只想给他看部分数据,而某些特殊的数据,比如采购的价格,则不会提供给他。再比如,人员薪酬是个敏感的字段,那么只给某个级别以上的人员开放,其他人的查询视图中则不提供这个字段。

视图的理解 :

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

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

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

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

  • 在数据库中,视图不会保存数据,数据真正保存在数据表中。当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化;反之亦然。

  • 视图,是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。

3.创建视图

#第十四章_视图(VIEW)
/*
1.视图的理解
​
 1> 视图, 可以看做是一个虚拟表, 本身是不存储数据的
        视图的本质, 就可以看做是存储起来的SELECT语句
​
 2> 视图中SELECT语句中涉及到的表, 称为基表
​
 3> 针对视图做DML操作, 会影响到对应的基表中的数据。反之亦然
​
 4> 视图本身的删除不会导致基表数据的删除。
​
 5> 视图的应用场景:针对小型项目, 不推荐使用视图, 针对于大型项目, 可以考虑使用视图。
​
 6> 视图的优点 : 简化查询;控制数据的访问
*/
​
# 2. 如何创建视图
CREATE DATABASE dbtest14;
​
USE dbtest14;
​
CREATE TABLE emps
AS
SELECT * FROM atguigudb.employees;
​
CREATE TABLE depts
AS
SELECT * FROM atguigudb.departments;
​
SELECT * FROM emps;
​
SELECT * FROM depts;
​
DESC emps;#CREATE TABLE AS 方式创建的表不会将主键约束复制过来 (除了非空约束都没有带过来)
​
#针对于单表
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 AS "emp_id", last_name AS "lname", salary
FROM emps#查询语句中的字段别名会作为视图中的字段
WHERE salary > 8000;
​
SELECT * FROM vu_emp2;
​
#确定视图中字段名的方式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;
​
#可以有原来表中不存在的数据(运算出来的)
CREATE VIEW vu_emp_sal
AS
SELECT department_id, AVG(salary) AS "avg_sal"
FROM emps
WHERE department_id IS NOT NULL
GROUP BY department_id;
​
SELECT * FROM vu_emp_sal;
​
#针对于多表
​
CREATE VIEW vu_emp_dept
AS
SELECT employee_id, e.department_id, department_name
FROM emps e JOIN depts d
ON e.department_id = d.department_id;
​
SELECT * FROM vu_emp_dept;
​
​
#利用视图对数据进行格式化
CREATE VIEW vu_emp_dept1
AS
SELECT CONCAT(e.last_name, '(', d.department_name, ')') AS "emp_info"
FROM emps e JOIN depts d
ON e.department_id = d.department_id;
​
SELECT * FROM vu_emp_dept1;
​
#基于视图创建视图
CREATE VIEW vu_emp4
AS
SELECT employee_id, last_name 
FROM vu_emp1;
​
SELECT * FROM vu_emp4;

4.查看视图

# 3. 查看视图
#查看数据库的表对象和视图对象
SHOW TABLES;
​
#查看视图的结构
DESC vu_emp1;
​
#查看视图的属性信息
SHOW TABLE STATUS LIKE 'vu_emp1';
​
#查看视图的详细定义信息
SHOW CREATE VIEW vu_emp1;

5.更新视图

#4. 更新(包括增删改)视图中的数据
# 一般情况下可以更新数据
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 * FROM vu_emp_sal;
​
#更新失败
UPDATE vu_emp_sal
SET avg_sal = 5000
WHERE department_id = 30;#error : The target table vu_emp_sal of the UPDATE is not updatable
#删除失败
DELETE FROM vu_emp_sal
WHERE department_id = 30;#error : The target table vu_emp_sal of the DELETE is not updatable
​
/*
总结 : 虽然视图可以更新数据, 但是视图作为虚拟表, 主要用于方便查询, 不建议更新视图数据。对视图
数据的修改都是通过实际数据表里数据的操作完成的。
*/

6.修改、删除视图

#5. 修改删除视图
DESC vu_emp1;
#修改 : 
#方式一 
CREATE OR REPLACE VIEW vu_emp1#如果没有就创建 如果有就替换
AS
SELECT employee_id, last_name, salary, email
FROM emps
WHERE salary > 7000;
​
DESC vu_emp1;
​
#方式二 : 
ALTER VIEW vu_emp1
AS
SELECT employee_id, last_name, salary, email, hire_date
FROM emps;
​
DESC vu_emp1;
​
#删除
SHOW TABLES;
​
DROP VIEW vu_emp4;
​
DROP VIEW IF EXISTS vu_emp2, vu_emp3;
​
#注意 : 如果是基于两个VIEW造出的VIEW如果将基表删除 会导致造出的VIEW查询失败 必须要修改视图

7.总结

视图优点 :

  • 操作简单

  • 减少数据冗余

  • 数据安全

  • 适应灵活多变的需求

  • 能够分解复杂的查询逻辑

视图不足 :

维护性的问题 : 如果基表的结构变更了,我们就需要及时对相关的视图进行相应的维护

如果视图过多,会导致数据库维护成本问题

如果覆盖的视图过多会导致数据不清晰

章节练习

#第十四章_章节练习
​
#练习一 :
#1. 使用表employees创建视图employee_vu 其中包括姓名,员工号,部门号
CREATE OR REPLACE VIEW employee_vu
AS 
SELECT last_name, employee_id, department_id
FROM atguigudb.employees;
​
​
#2. 显示视图的结构 
DESC employee_vu;
​
#3. 查询视图中的全部内容 
SELECT * FROM employee_vu;
​
#4. 将视图中的数据限定在部门号是80的范围内
ALTER VIEW employee_vu
AS
SELECT last_name, employee_id, department_id
FROM atguigudb.employees
WHERE department_id = 80;
​
SELECT * FROM employee_vu;
​
#练习二 : 
#1. 创建视图emp_v1,要求查询电话号码以‘011’开头的员工姓名和工资、邮箱
USE dbtest14;
DESC emps;
​
CREATE VIEW emp_v1
AS
SELECT last_name, salary, email
FROM emps
WHERE phone_number LIKE '011%'; 
​
SELECT * FROM emp_v1;
​
#2. 要求将视图 emp_v1 修改为查询电话号码以‘011’开头的并且邮箱中包含 e 字符的员工姓名和工资 邮箱、电话号码 
CREATE OR REPLACE VIEW emp_v1
AS
SELECT last_name, salary, email, phone_number
FROM emps
WHERE phone_number LIKE '011%' AND email REGEXP '[e]'; 
​
SELECT * FROM emp_v1;
​
#3. 向 emp_v1 插入一条记录,是否可以? 
INSERT INTO emp_v1
VALUES ('Partners', 2300, 'AERRAZUR', '011.44.1344.467268');
#Field of view 'dbtest14.emp_v1' underlying table doesn't have a default value
#说明 : 向视图中插入就相当于向基表中插入 但是由于字段不全 其他的视图没有的字段
#可能会有NOT NILL的约束 所以会插入失败
​
#4. 修改emp_v1中员工的工资,每人涨薪1000 
UPDATE emp_v1
SET salary = salary + 1000;
​
SELECT * FROM emp_v1;
​
#5. 删除emp_v1中姓名为Olsen的员工 
DELETE FROM emp_v1
WHERE last_name = 'Olsen';
​
​
#6. 创建视图emp_v2,要求查询部门的最高工资高于 12000 的部门id和其最高工资 
CREATE OR REPLACE VIEW emp_v2(dept_id, max_sal)
AS
SELECT department_id, MAX(salary)
FROM emps
GROUP BY department_id
HAVING MAX(salary) > 12000;
​
SELECT * FROM emp_v2;
​
​
#7. 向 emp_v2 中插入一条记录,是否可以? 
INSERT INTO emp_v2
VALUES (1000, 12345);#The target table emp_v2 of the INSERT is not insertable-into
#模棱两可不能添加
​
#8. 删除刚才的emp_v2 和 emp_v1
​
DROP VIEW IF EXISTS emp_v2, emp_v1;
​
SHOW TABLES;

十五、存储过程与函数

1.存储过程概述

用户自定义的函数。存储过程和函数, 可以将复杂的SQL逻辑封装在一起, 应用程序无需关注存储过程和函数内部复杂的SQL逻辑, 只需要简单的调用存储过程和函数即可。

好处

1、简化操作,提高了sql语句的重用性,减少了开发程序员的压力

2、减少操作过程中的失误,提高效率

3、减少网络传输量(客户端不需要把所有的 SQL 语句通过网络发给服务器)

4、减少了 SQL 语句暴露在网上的风险,也提高了数据查询的安全性

5、相较于函数,存储过程是没有返回值的

分类

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

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

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

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

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

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

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

2.创建存储过程与调用

#0. 准备工作
CREATE DATABASE dbtest15;
​
USE dbtest15;
​
CREATE TABLE IF NOT EXISTS employees
AS
SELECT * 
FROM atguigudb.employees;
​
CREATE TABLE IF NOT EXISTS departments
AS 
SELECT *
FROM atguigudb.departments;
​
SELECT * FROM employees;
​
SELECT * FROM departments;
​
#1. 创建存储过程
#类型一 : 无参数 无返回值
#举例1:创建存储过程select_all_data(),查看 emps 表的所有数据
DELIMITER $
​
CREATE PROCEDURE select_all_data1()
BEGIN
            SELECT * FROM employees;
END $
​
DELIMITER ;
​
#举例2:创建存储过程avg_employee_salary(),返回所有员工的平均工资
DELIMITER //
​
CREATE PROCEDURE avg_employee_salary()
BEGIN
            SELECT AVG(salary) FROM employees;
END //
​
DELIMITER ;
​
#调用
CALL avg_employee_salary();
​
#举例3:创建存储过程show_max_salary(),用来查看“emps”表的最高薪资值。
DELIMITER //
​
CREATE PROCEDURE show_max_salary()
BEGIN
            SELECT MAX(salary) FROM employees;
END //
​
DELIMITER ;
​
CALL show_max_salary();
​
#类型二 : 带OUT的
#举例4:创建存储过程show_min_salary(),查看“emps”表的最低薪资值。并将最低薪资通过OUT参数“ms”
DELIMITER //
​
CREATE PROCEDURE show_min_salary(OUT ms DOUBLE)
BEGIN 
            SELECT MIN(salary) INTO ms
            FROM employees;
END //
​
DELIMITER ;
​
#调用
CALL show_min_salary(@ms);#加@表明是用户定义的变量
​
#查看变量值
SELECT @ms;
​
#带IN的
#举例5:创建存储过程show_someone_salary(),查看“emps”表的某个员工的薪资,并用IN参数empname
#输入员工姓名。
DELIMITER //
​
CREATE PROCEDURE show_someone_salary(IN empname VARCHAR(20))
BEGIN 
            SELECT salary FROM employees
            WHERE last_name = empname;
END //
​
​
DELIMITER ;
​
#调用方式一 : 
CALL show_someone_salary('Abel');
​
#调用方式二 :
SET @empname := 'Abel';
CALL show_someone_salary(@empname)
​
#带IN 带OUT的
​
#举例6:创建存储过程show_someone_salary2(),查看“emps”表的某个员工的薪资,并用IN参数empname
#输入员工姓名,用OUT参数empsalary输出员工薪资。
DELIMITER //
CREATE PROCEDURE show_someone_salary2(IN empname VARCHAR(15), OUT empsalary DOUBLE)
BEGIN 
            SELECT salary INTO empsalary
            FROM employees
            WHERE last_name = empname;
END //
​
DELIMITER ;
​
#调用
SET @empname = 'Abel';
​
CALL show_someone_salary2(@empname, @empsalary);
​
SELECT @empsalary;
​
#带INOUT的
#举例7:创建存储过程show_mgr_name(),查询某个员工领导的姓名,并用INOUT参数“empname”输入员
#工姓名,输出领导的姓名。
DELIMITER //
​
CREATE PROCEDURE show_mgr_name(INOUT empname VARCHAR(25))
BEGIN 
            SELECT last_name INTO empname
            FROM employees
            WHERE employee_id = (
                                SELECT manager_id 
                                FROM employees
                                WHERE last_name = empname
                                 );
END //
​
DELIMITER ;
​
#调用 
SET @empname := 'Abel';
​
CALL show_mgr_name(@empname);
​
SELECT @empname;

3.存储函数概述

MySQL支持用户自定义的函数, 定义好以后, 调用方式与调用系统预定义的系统函数方式一样。

语法分析

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。

4.创建存储函数与调用

#第十五章_存储过程与存储函数
​
USE dbtest15;
​
#举例1:创建存储函数,名称为email_by_name(),参数定义为空,该函数查询Abel的email,并返回,数据类型为
#字符串型。
​
DELIMITER //
​
CREATE FUNCTION email_by_name()
RETURNS VARCHAR(25)
#在别的可视化软件 sqlyog可能会报错 因为没加特征
BEGIN 
            RETURN(SELECT email FROM employees WHERE last_name = 'Abel');
END //
​
​
DELIMITER ;
​
SELECT email_by_name() FROM DUAL;
​
#举例2 : 创建存储函数,名称为email_by_id(),参数传入emp_id,该函数查询emp_id的email,并返回,数据类型
#为字符串型。
​
DELIMITER //
CREATE FUNCTION email_by_id(emp_id INT)#不用加IN 默认全是IN
RETURNS VARCHAR(25)
BEGIN 
            RETURN(SELECT email FROM employees WHERE employee_id = emp_id);
END //
​
DELIMITER ;
​
#调用
SELECT email_by_id(101) FROM DUAL;
​
SET @emp_id := 102;
SELECT email_by_id(@emp_id)
FROM DUAL;
​
SELECT * FROM employees;
​
#举例3 : 创建存储函数count_by_id(),参数传入dept_id,该函数查询dept_id部门的员工人数,并返回,数据类型
#为整型。
​
DELIMITER //
CREATE FUNCTION count_by_id(dept_id INT)
RETURNS INT
BEGIN
            RETURN(SELECT COUNT(*) FROM employees WHERE department_id = dept_id);
END //
​
DELIMITER ;
​
#调用
SET @dept_id := 30;
SELECT count_by_id(@dept_id);

5.对比存储过程与存储函数

关键字调用语法返回值应用场景
存储过程PROCEDURECALL 存储过程()理解为有0个或者多个返回值一般用于更新
存储函数FUNCTIONSELECT 存储函数()只能是一个一般用于查询

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

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

#3. 存储过程和函数的查看
​
#1. 使用SHOW CREATE 语句查看存储过程和函数的创建信息
SHOW CREATE PROCEDURE show_mgr_name;
​
SHOW CREATE FUNCTION count_by_id;
​
#2. 使用SHOW STATUS 语句查看存储过程和存储函数的状态信息
SHOW PROCEDURE STATUS;#查看的是全部的存储过程
​
SHOW PROCEDURE STATUS LIKE 'show_max_salary';#模糊查询
​
SHOW FUNCTION STATUS LIKE 'email_by_id';
​
#3. 从information_schema.Routines表中查看存储过程和函数的信息
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_NAME = 'email_by_id';#存储函数
​
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_NAME = 'email_by_id' AND ROUTINE_TYPE = 'function';
​
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_NAME = 'show_min_salary';#存储过程
#修改存储过程和函数,不影响存储过程和函数的功能,只是修改相关的特性。使用ALTER语句实现
SHOW CREATE PROCEDURE show_max_salary;
​
ALTER PROCEDURE show_max_salary
SQL SECURITY INVOKER
COMMENT '查询最高工资';
#5. 删除
DROP FUNCTION IF EXISTS count_by_id;
​
DROP PROCEDURE IF EXISTS show_min_salary;

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

优点 :

  • 存储过程可以一次编译多次使用

  • 可以减少开发工作量

  • 存储过程的安全性强

  • 可以减少网络传输量

  • 良好的封装性

缺点 :

  • 可移植性差

  • 调试困难

  • 存储过程的版本管理很难

  • 不适合高并发的场景

章节练习

#15_存储过程与函数章节练习
​
CREATE DATABASE IF NOT EXISTS test15_pro_func CHARACTER SET 'utf8';
​
USE test15_pro_func;
​
#存储过程
​
#1. 创建存储过程insert_user(),实现传入用户名和密码,插入到admin表中
CREATE TABLE admin( 
id INT PRIMARY KEY AUTO_INCREMENT, 
user_name VARCHAR(15) NOT NULL, 
pwd VARCHAR(25) NOT NULL
);
​
DELIMITER //
CREATE PROCEDURE insert_user(IN username VARCHAR(15), IN loginPwd VARCHAR(25))
BEGIN 
            INSERT INTO admin(user_name, pwd)
            VALUES (username, loginPwd);
END //
​
DELIMITER ;
​
#调用
CALL insert_user('Tom', 'abc123');
SELECT * FROM admin;
 
#2. 创建存储过程get_phone(),实现传入女神编号,返回女神姓名和女神电话
CREATE TABLE beauty( 
id INT PRIMARY KEY AUTO_INCREMENT, 
NAME VARCHAR(15) NOT NULL, 
phone VARCHAR(15) UNIQUE, 
birth DATE 
);
​
INSERT INTO beauty(NAME,phone,birth) 
VALUES 
('朱茵','13201233453','1982-02-12'), 
('孙燕姿','13501233653','1980-12-09'), 
('邓紫棋','17843283452','1991-11-12'), 
('刘若英','18635575464','1989-05-18'), 
('杨超越','13761238755','1994-05-11'); 
​
SELECT * FROM beauty;
​
DELIMITER //
​
CREATE PROCEDURE get_phone(IN beauty_id INT, OUT phone VARCHAR(15), OUT name VARCHAR(15))
BEGIN
            SELECT name, phone INTO name, phone
            FROM beauty
            WHERE id = beauty_id;
END //
​
DELIMITER ;
​
DROP PROCEDURE get_phone;
​
CALL get_phone(3, @phone, @name);
​
SELECT @name, @phone;
​
#3. 创建存储过程date_diff(),实现传入两个女神生日,返回日期间隔大小
DELIMITER //
CREATE PROCEDURE date_diff(IN beauty1_birth DATE, IN beauty2_birth DATE, OUT sum_date INT)
BEGIN 
            SELECT DATEDIFF(beauty1_birth, beauty2_birth) INTO sum_date FROM DUAL;
END //
​
DELIMITER ;
​
CALL date_diff('1994-05-11', '1982-02-12', @sum_date);
​
SELECT @sum_date;
​
#4. 创建存储过程format_date(),实现传入一个日期,格式化成xx年xx月xx日并返回
DELIMITER //
CREATE PROCEDURE format_date(IN my_date DATE, OUT str_date VARCHAR(25))
BEGIN
            SELECT DATE_FORMAT(my_date,'%y年%m月%d日') INTO str_date;
END //
​
DELIMITER ;
​
CALL format_date('1994-05-11', @str_date);
​
SELECT @str_date;
​
#5. 创建存储过程beauty_limit(),根据传入的起始索引和条目数,查询女神表的记录
DELIMITER //
CREATE PROCEDURE beauty_limit(IN `s_index` INT, IN size INT)
BEGIN
            SELECT * FROM beauty LIMIT s_index, size;
​
END //
​
DELIMITER ;
​
CALL beauty_limit(2, 2);
​
#创建带inout模式参数的存储过程
#6. 传入a和b两个值,最终a和b都翻倍并返回 
DELIMITER //
CREATE PROCEDURE a_b_double(INOUT a INT, INOUT b INT)
BEGIN
            SET a = a * 2;
            SET b = b * 2;
END //
​
DELIMITER ;
​
SET @a := 5, @b := 6;
CALL a_b_double(@a, @b);
​
SELECT @a, @b;
​
#7. 删除题目5的存储过程 
DROP PROCEDURE beauty_limit();
​
#8. 查看题目6中存储过程的信息
SHOW CREATE PROCEDURE a_b_double;
​
SHOW PROCEDURE STATUS LIKE 'a_b_%';
​
#存储函数
​
#0. 准备工作 
USE test15_pro_func; 
​
CREATE TABLE employees 
AS
SELECT * FROM atguigudb.`employees`; 
​
CREATE TABLE departments 
AS
SELECT * FROM atguigudb.`departments`;
​
#无参有返回 
#1. 创建函数get_count(),返回公司的员工个数
DELIMITER //
CREATE FUNCTION get_count()
RETURNS INT
BEGIN 
            RETURN (SELECT COUNT(*) FROM employees);
END //
​
DELIMITER ;
​
SELECT get_count() FROM DUAL;
​
#有参有返回 
DESC employees;
DESC departments;
​
#2. 创建函数ename_salary(),根据员工姓名,返回它的工资 
DELIMITER //
CREATE FUNCTION ename_salary(emp_name VARCHAR(15))
RETURNS DOUBLE(8, 2)
BEGIN
            RETURN(SELECT salary FROM employees WHERE last_name = emp_name);
END //
​
DELIMITER ;
​
SET @emp_name := 'Abel';
SELECT ename_salary(@emp_name) FROM DUAL;
​
#3. 创建函数dept_sal() ,根据部门名,返回该部门的平均工资 
DELIMITER //
CREATE FUNCTION dept_sal(dept_name VARCHAR(30))
RETURNS DOUBLE(8, 2)
BEGIN
            RETURN(SELECT AVG(salary)
                   FROM employees
                   GROUP BY department_id 
                   HAVING department_id = (
                                           SELECT department_id 
                                           FROM departments
                                           WHERE department_name = 'IT'
                                       ));
END //
​
DELIMITER ;
​
SELECT dept_sal('IT') FROM DUAL;
​
#多表查询
SELECT AVG(salary)
FROM employees e JOIN departments d
ON e.department_id = d.department_id
WHERE department_name = 'IT';
​
#4. 创建函数add_float(),实现传入两个float,返回二者之和
DELIMITER //
CREATE FUNCTION add_float(num1 FLOAT, num2 FLOAT)
RETURNS FLOAT
BEGIN
            RETURN (SELECT num1 + num2);
END // 
 
DELIMITER ;
​
SELECT add_float(5.12, 5.13);

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

1.变量

在MySQL数据库的存储过程与函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。在MySQL数据库中,变量分为系统变量用户自定义变量

1.1系统变量

系统变量分类

全局系统变量 和 会话系统变量

全局系统变量是启动服务器之后赋值的系统变量

会话系统变量是建立连接之后(MySQL实例)赋值的系统变量

有的变量既是全局系统变量也是会话系统变量

#第十六章_变量_流程控制与游标
​
#1.变量:
#系统变量(全局系统变量与会话系统变量) VS 用户自定义变量
​
# 1.1 查看系统变量
​
#查看全局的系统变量
SHOW GLOBAL VARIABLES;#617
​
#查看会话的系统变量 (可加模糊查询)
SHOW SESSION VARIABLES;
#或
SHOW VARIABLES;#640
​
#查看字符集的变量
SHOW VARIABLES LIKE 'character_%';
​
# 1.2查看指定系统变量
#@@global 或 @@session
#如果没写全局或者是会话 那么就会先从会话中的变量找 没找到再去全局的找
​
SELECT @@global.变量名;
SELECT @@global.MAX_CONNECTIONS;
SELECT @@global.character_set_client;#global级别的变量
​
SELECT @@session.变量名;
SELECT @@session.character_set_client;#session级别的变量
SELECT @@session.pseudo_thread_id;#用于标记当前会话的MySQL连接ID
​
SELECT @@变量名;
SELECT @@character_set_client;#查询的是会话的系统变量 因为先查会话的系统变量
​
#1.3修改系统变量的值
#全局系统变量
#方法一 : 直接修改配置文件 重启服务器
#方式二 : 在MySQL服务运行期间, 使用'SET'命令重新设置系统变量的值
​
    #方式一 : 
SET @@global.MAX_CONNECTIONS = 161;
SELECT @@global.max_connections;
    #方式二 :
SET GLOBAL max_connections = 171;
SELECT @@global.max_connections;
    #上述方式一旦重启服务器就会失效
​
#会话系统变量
#方式一 : 
SET @@session.character_set_client = 'gbk';
#方式二 :
SET SESSION character_set_client = 'utf8mb3';
#换一个会话就会失效

1.2用户变量

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

  • 会话用户变量 : 作用域和会话变量一样, 只对当前连接的变量有效

  • 局部变量 : 只在BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用

#2. 用户变量
# 会话用户变量 VS 局部变量
​
#2.1 会话用户变量
​
#准备工作
CREATE DATABASE IF NOT EXISTS dbtest16 CHARACTER SET = 'utf8';
​
USE dbtest16;
​
CREATE TABLE employees
AS
SELECT * FROM atguigudb.employees;
​
CREATE TABLE departments
AS
SELECT * FROM atguigudb.departments;
​
SELECT * FROM employees;
SELECT * FROM employees;
​
#变量的定义 
#方式一 : "=" 或 ":="
​
SET @m1 = 1;
SET @m2 := 2;
SET @sum := @m1 + @m2;
​
SELECT @sum;
​
#方式二 : ":=" 或 INTO关键字 
SELECT @count := COUNT(*) FROM employees;
SELECT @count;
​
SELECT AVG(salary) INTO @avg_sal FROM employees;
SELECT @avg_sal;
​
#2.2 局部变量
/*
局部变量必须满足 : 
必须使用DECLARE声明 
声明并使用在BEGIN...END中(存储过程和存储函数)
DECLARE的方式声明的局部变量必须声明在BEGIN中的首行位置
​
定义方式 :
DECLARE 变量名 类型 [default 值]; # 如果没有DEFAULT子句,初始值为NULL
​
变量赋值 :
SET 变量名=值; 
SET 变量名:=值;
​
SELECT 字段名或表达式 INTO 变量名 FROM 表;
​
使用变量 :
SELECT 局部变量名;
*/
​
#举例
DELIMITER //
CREATE PROCEDURE test_var()
BEGIN
            #1.声明局部变量
            DECLARE a INT DEFAULT 0;
            DECLARE b INT;
            DECLARE emp_name VARCHAR(25);
​
            #2.赋值
            SET a = 1;
            SET b := 2;
​
            SELECT last_name INTO emp_name FROM employees WHERE employee_id = 101;
​
            #3.使用
            SELECT a, b, emp_name;
END //
​
DELIMITER ;
​
#调用
CALL test_var();
​
​
#举例3:创建存储过程“different_salary”查询某员工和他领导的薪资差距,并用IN参数emp_id接收员工
#id,用OUT参数dif_salary输出薪资差距结果。
​
​
DELIMITER //
CREATE PROCEDURE different_salary(IN emp_id INT, OUT dif_salary DOUBLE(8, 2))
BEGIN 
            DECLARE mar_id INT;
            DECLARE mar_sal, emp_sal DOUBLE(8, 2);
​
            SELECT manager_id INTO mar_id FROM employees WHERE employee_id = emp_id;
            SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
            SELECT salary INTO mar_sal FROM employees WHERE employee_id = mar_id;
                    
            SET dif_salary = mar_sal - emp_sal;
END //
​
DELIMITER ;
​
SET @emp_id = 101;
SET @dif_sal = 0;
​
CALL different_salary(@emp_id, @dif_sal);
​
SELECT @dif_sal FROM DUAL;

2.定义条件与处理程序

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

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

2.1案例分析

#2.1 错误演示 :
USE dbtest16;
#[Err] 1364 - Field 'email' doesn't have a default value
INSERT INTO employees(last_name)
VALUES 
('Tom');



#2.2 
DELIMITER //
CREATE PROCEDURE UpdateDataNoCodition()
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 ;

#调用存储过程
#错误代码 : [Err] 1048 - Column 'email' cannot be null
CALL UpdateDataNoCodition();

SELECT @x;# res:1

2.2定义条件

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

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。

#格式 : DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
​
#举例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';

2.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 编写的复合语句。

2.4案例解决

#2.4 案例的解决
DROP PROCEDURE UpdateDataNoCodition;
#重新定义存储过程
​
DELIMITER //
CREATE PROCEDURE UpdateDataNoCodition()
BEGIN
            #声明处理程序
            DECLARE CONTINUE HANDLER FOR 1048 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 UpdateDataNoCodition();
​
#查看变量
SELECT @prc_value, @x;#-1, 3

3.流程控制

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

  • 顺序结构 :程序从上往下依次执行

  • 分支结构 :程序按条件进行选择执行,从两条或多条路径中选择一条执行

  • 循环结构 :程序满足一定条件下,重复执行一组语句

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

  • 条件判断语句 :IF 语句和 CASE 语句

  • 循环语句 :LOOP、WHILE 和 REPEAT 语句

  • 跳转语句 :ITERATE 和 LEAVE 语句

3.1分支结构之IF

#3 流程控制
​
USE dbtest16;
​
SHOW TABLES;
#3.1 分支结构之IF
​
#举例 1
DELIMITER //
CREATE PROCEDURE test_if()
BEGIN
            /*情况一
            #声明局部变量
            DECLARE stu_name VARCHAR(15);
            
            IF stu_name IS NULL
                            THEN SELECT 'stu_name is null';
            END IF;*/
            /*情况二 : 二选一
            DECLARE email VARCHAR(25) DEFAULT 'jchuan_Hou@163.com';
            
            IF email IS NULL 
                            THEN SELECT 'email is null';
            ELSE 
                            SELECT 'email is not null';
            END IF;*/
​
            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;
​
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_salary DOUBLE;
            DECLARE hire_year DOUBLE;
            
            SELECT salary INTO emp_salary FROM employees WHERE emp_id = employee_id;
            SELECT DATEDIFF(CURRENT_DATE(), hire_date) / 365 INTO hire_year
            FROM employees WHERE emp_id = employee_id;
            
            IF emp_salary < 8000 && hire_year > 5.0 
                                    THEN UPDATE employees SET salary = salary + 500 WHERE emp_id = employee_id;
            END IF;
​
END //
​
DELIMITER ;
​
#调用
CALL update_salary_by_eid1(104);
​
SELECT last_name, salary
FROM employees
WHERE employee_id = 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_salary DOUBLE;
            DECLARE emp_commission_pct DOUBLE;
            #变量赋值
            SELECT salary INTO emp_salary FROM employees WHERE emp_id = employee_id;
            SELECT commission_pct INTO emp_commission_pct FROM employees 
            WHERE emp_id = employee_id;
​
                #执行语句
                IF emp_salary < 9000 
                THEN UPDATE employees SET salary = 9000 WHERE emp_id = employee_id;
                ELSEIF emp_salary >= 9000 && emp_salary < 10000 && emp_commission_pct IS NULL 
                THEN UPDATE employees SET commission_pct  = 0.01 WHERE emp_id = employee_id;
                ELSE 
                    UPDATE employees SET salary = salary + 100 WHERE emp_id =employee_id;
                END IF;
END //
​
DELIMITER ; 
​
DESC employees;
​
DROP PROCEDURE update_salary_by_eid3;
​
SELECT * FROM employees
WHERE employee_id = 103;
​
#调用
CALL update_salary_by_eid3(104);
CALL update_salary_by_eid3(102);
CALL update_salary_by_eid3(103);

3.2分支结构之CASE

#3.2 分支结构之CASE
​
#举例
DELIMITER //
CREATE PROCEDURE test_case()
BEGIN 
            /*情况一 : 相当于 SWITCH
            DECLARE var INT DEFAULT 2;
            
            CASE var    
                WHEN 1 THEN SELECT 'var is 1';
                WHEN 2 THEN SELECT 'var is 2';
                WHEN 3 THEN SELECT 'var is 3';
                ELSE SELECT 'var is others';
            END CASE;*/
​
            #情况二 : case when ...then ...
            DECLARE var INT DEFAULT 10;
            CASE 
                WHEN var >= 100 THEN SELECT '三位数';
                WHEN var >= 10 THEN SELECT '两位数';
                ELSE SELECT '一位数';
            END CASE;
        
END //
​
DELIMITER ;
​
#调用
CALL test_case();
​
DROP PROCEDURE test_case;
​
​
#举例2:声明存储过程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 DOUBLE;
    #变量赋值
    SELECT ROUND(DATEDIFF(CURRENT_DATE(), hire_date) / 365) INTO hire_year FROM employees 
    WHERE emp_id = employee_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 ;
​
DROP PROCEDURE update_salary_by_eid5;
​
#调用
CALL update_salary_by_eid5(103);
​
SELECT salary FROM employees WHERE employee_id = 103;

3.3循环结构之LOOP

#3.3 循环结构之LOOP
DELIMITER //
CREATE PROCEDURE test_loop()
BEGIN 
        #声明局部变量
        DECLARE num INT DEFAULT 1;
        
        loop_label:LOOP #可以不加 loop_label:
            #重新赋值
            SET num = num + 1;
            IF num >= 10 THEN LEAVE loop_label;
            END IF;
        END LOOP loop_label;#可以不加 loop_label
​
        #查看值
        SELECT num FROM DUAL;
​
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_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 = 1.2 * salary;
            SELECT AVG(salary) INTO avg_salary FROM employees;
            SET loop_count = loop_count + 1;
        END LOOP label_loop;
        
        SET num = loop_count;
END //
​
DELIMITER ;
​
#调用
SET @num = 0;
​
CALL update_salary_loop(@num);
​
SELECT @num;
​
SELECT AVG(salary) FROM employees;

3.4循环结构之WHILE

#3.4 循环结构之WHILE
/*
凡是循环结构 : 一定具备四个结构
1. 初始化条件
2. 循环条件
3. 循环体
4. 迭代条件
*/
​
#举例
​
DELIMITER //
​
CREATE PROCEDURE test_while()
BEGIN
            #初始化条件
            DECLARE num INT DEFAULT 0;
            #循环条件
            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 cnt INT)
BEGIN 
        #初始化条件
        DECLARE num INT DEFAULT 0;
        DECLARE avg_salary DOUBLE;
​
        SELECT AVG(salary) INTO avg_salary FROM employees;
        
        #循环条件
        WHILE avg_salary >= 5000 DO
                #循环体
                UPDATE employees SET salary = salary * 0.9;
            
                #迭代条件
                SET num = num + 1;
                SELECT AVG(salary) INTO avg_salary FROM employees;
        END WHILE;
        
        SET cnt = num;
END //
​
DELIMITER ;
​
#调用
SELECT AVG(salary) FROM employees;
​
SET @num = 0;
​
CALL update_salary_while(@num);
​
SELECT @num;

3.5循环结构之REPEAT

#3.5 循环结构REPEAT
​
#举例
​
DELIMITER //
CREATE PROCEDURE test_repeat()
BEGIN
        #声明变量
        DECLARE num INT DEFAULT 1;
​
        REPEAT 
                SET num = num + 1;
                UNTIL num >= 10#不能加分号
        END REPEAT;
        
        #查看
        SELECT num;
END //
​
DELIMITER ;
​
#调用
DROP PROCEDURE test_repeat;
​
CALL test_repeat;
​
​
#举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程
#“update_salary_repeat()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨
#为原来的1.15倍。直到全公司的平均薪资达到13000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_repeat(OUT num INT)
BEGIN
        #初始化条件
        DECLARE cnt INT DEFAULT 0;
        DECLARE avg_salary DOUBLE;
​
        SELECT AVG(salary) INTO avg_salary FROM employees;
​
        REPEAT 
                #循环体 
                UPDATE employees SET salary = salary * 1.15;
​
                #迭代条件
                SET cnt = cnt + 1;
                SELECT AVG(salary) INTO avg_salary FROM employees;
        UNTIL avg_salary >= 13000
        END REPEAT;
​
        SET num = cnt;
END //
​
DELIMITER ;
​
SELECT AVG(salary) FROM employees;
​
SET @num = 0;
​
CALL update_salary_repeat(@nun);
​
SELECT @num;

对比三种循环结构:

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

  • LOOP:一般用于实现简单的"死"循环

  • WHILE:先判断后执行

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

3.6跳转语句之LEAVE语句

相当于C语言中的BREAK

LOOP循环语句只能用LEAVE终止循环

LEAVE后面一定加的是标签

#3.6 跳转语句之LEAVE语句
​
#举例
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 ;
​
#调用
CALL leave_begin(1);#查平均工资
CALL leave_begin(0);#跳出

3.7跳转语句之ITERATE

相当于java中的continue

只能在循环中使用

#3.7 跳转语句之ITERATE
/*
举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
如果num < 10,则继续执行循环;
如果num > 15,则退出循环结构;
*/
​
DELIMITER //
CREATE PROCEDURE test_iterate()
BEGIN
        #定义局部变量
        DECLARE num INT DEFAULT 0;
        label_loop : LOOP
                #迭代
                SET num = num + 1;
                
                IF num < 10 THEN ITERATE label_loop;
                ELSEIF num > 15 THEN LEAVE label_loop;
                END IF;#可以直接END IF 不用ELSE
​
                SELECT 'Jingchuan_Hou' FROM DUAL;#判断循环执行位置
​
        END LOOP label_loop;
        
        SELECT num FROM DUAL;
        
END //
​
DELIMITER ;
​
#调用
DROP PROCEDURE test_iterate;
​
CALL test_iterate();

小结 :

  • LEAVE 可以使用在循环中和BEGIN END 中 含义为BREAK 跳出循环或存储函数存储过程

  • ITERATE 只能用在循环中 含义为CONTINUE 跳过本次循环

  • 上述两种语法后面必须跟标签名称

4.游标

4.1什么是游标

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

游标可以对结果集中的每一条记录进行定位,游标让SQL这种面向集合的语言有了面向过程的开发能力。

游标在SQL中充当了C语言中指针的作用,可以利用操作游标来对数据行进行操作。

4.2使用游标的步骤

#第十六章_变量、流程控制与游标
#游标
USE dbtest16;
​
#2. 使用CURSOR的四步骤
/*
1. 声明
2. 打开
3. 使用
4. 关闭
*/
​
#举例 : 创建存储过程“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 DEFAULT 0.0;
            DECLARE emp_count INT DEFAULT 0;
​
            #1. 声明游标
            DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
            #2. 打开游标 : 游标指向第一条记录的前面
            OPEN emp_cursor;
​
            WHILE sum_sal <= limit_total_salary DO
                    #3. 使用游标 : 游标每使用一次自动跳转下一行记录 游标只读 不能跳记录
                    FETCH emp_cursor INTO emp_sal;
                    
                    SET sum_sal = sum_sal + emp_sal;
                    SET emp_count = emp_count + 1;
            END WHILE;
​
            #4. 关闭游标 
            CLOSE emp_cursor;
​
            SET total_count = emp_count;
​
END //
​
DELIMITER ;
​
#调用
CALL get_count_by_limit_total_salary(200000, @total_count);
​
SELECT @total_count;
​
SELECT SUM(salary) FROM employees ORDER BY salary DESC LIMIT 0, 4;

小结 :

游标是 MySQL 的一个重要的功能,为 逐条读取 结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行 加锁 ,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理。

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

十七、触发器

在实际开发中,我们经常会遇到这样的情况:有 2 个或者多个相互关联的表,如 商品信息 和 库存信息 分别存放在 2 个不同的数据表中,我们在添加一条新商品记录的时候,为了保证数据的完整性,必须同时在库存表中添加一条库存记录。

这样一来,我们就必须把这两个关联的操作步骤写到程序里面,而且要用 事务 包裹起来,确保这两个操作成为一个 原子操作 ,要么全部执行,要么全部不执行。要是遇到特殊情况,可能还需要对数据进行手动维护,这样就很 容易忘记其中的一步 ,导致数据缺失。

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

1.触发器概述

MySQL从 5.0.2 版本开始支持触发器。MySQL的触发器和存储过程一样,都是嵌入到MySQL服务器的一段程序。

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

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

2.触发器的创建

语法结构

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

说明:

  • 表名 :表示触发器监控的对象。

  • BEFORE|AFTER :表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。

  • INSERT|UPDATE|DELETE :表示触发的事件。

    • INSERT 表示插入记录时触发;

    • UPDATE 表示更新记录时触发;

    • DELETE 表示删除记录时触发。

  • 触发器执行的语句块 :可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块。

#第十七章_触发器
​
#准备工作
​
CREATE DATABASE dbtest17;
​
USE dbtest17;
​
#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)
);
​
#查看数据
SELECT * FROM test_trigger;
​
SELECT * FROM test_trigger_log;
​
#创建触发器:
#创建名称为before_insert_test_tri的触发器,向test_trigger数据表插入数据之前,向
#test_trigger_log数据表中插入before_insert的日志信息。
​
DELIMITER //
​
CREATE TRIGGER before_insert_test_tri
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN 
    INSERT INTO test_trigger_log(t_log) 
    VALUES ('before insert ...');
END //
​
DELIMITER ;
​
#测试 
INSERT INTO test_trigger(t_note)
VALUES ('Tom');
​
#查看数据
SELECT * FROM test_trigger;
​
SELECT * FROM test_trigger_log;#触发器起作用
​
#举例:定义触发器“salary_check_trigger”,基于员工表“employees”的INSERT事件,在INSERT之前检查
#将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报sqlstate_value为'HY000'的错
#误,从而使得添加失败。
​
CREATE TABLE employees
AS
SELECT * FROM atguigudb.employees;
​
CREATE TABLE departments
AS
SELECT * FROM atguigudb.departments;
​
#创建触发器
DELIMITER //
​
CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
        #查询到要添加的数据的mar的薪资
        DECLARE mar_sal DOUBLE;
        #使用NEW来表示要插入的一行记录
        SELECT salary INTO mar_sal FROM employees WHERE employee_id = NEW.manager_id;
​
        IF NEW.salary > mar_sal 
                    THEN SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
        END IF;
        
END //
​
DELIMITER ;
​
#上面触发器的声明过程中的NEW关键字代表INSERT添加语句的新记录
​
#测试
DESC employees;
​
INSERT INTO employees(employee_id, last_name, email, hire_date, job_id, salary, manager_id)
VALUES (300, 'Tom', 'tom@126.com', NOW(), 'AD_VP', 8000, 103);
​
SELECT * FROM employees;
​
INSERT INTO employees(employee_id, last_name, email, hire_date, job_id, salary, manager_id)
VALUES (301, 'Tom1', 'tom1@126.com', NOW(), 'AD_VP', 10000, 103);

3.查看和删除触发器

#2. 查看和删除触发器
​
#查看当前数据库的所有触发器的定义
SHOW TRIGGERS;
​
#查看当前数据库中摸个触发器的定义
SHOW CREATE TRIGGER salary_check_trigger;
​
#从系统库中查看信息
SELECT * FROM information_schema.`TRIGGERS`;
​
#3. 删除触发器
DROP TRIGGER salary_check_trigger;
​
SHOW TRIGGERS;

4.触发器的优缺点

优点 :

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

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

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

缺点 :

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

  • 相关数据的更新, 可能会导致触发器出错

注意 :

注意,如果在子表中定义了外键约束,并且外键指定了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。

章节练习

#第十七章_触发器章节练习
​
#练习一 : 
#0. 准备工作 
USE dbtest17;
​
CREATE TABLE emps 
AS
SELECT employee_id,last_name,salary 
FROM atguigudb.`employees`; 
​
#1. 复制一张emps表的空表emps_back,只有表结构,不包含任何数据 
CREATE TABLE emps_back
AS
SELECT * FROM emps
WHERE 1 = 2;
#2. 查询emps_back表中的数据 
SELECT * FROM emps_back;
​
#3. 创建触发器emps_insert_trigger,每当向emps表中添加一条记录时,同步将这条记录添加到emps_back表 中
DELIMITER //
CREATE TRIGGER emps_insert_trigger
AFTER INSERT ON emps
FOR EACH ROW
BEGIN 
        INSERT INTO emps_back
        VALUES(NEW.employee_id, NEW.last_name, NEW.salary);
END //
​
DELIMITER ;
​
#4. 验证触发器是否起作用
​
INSERT INTO emps
VALUES (300, 'Tom', 1600);
​
SELECT * FROM emps_back;
​
#练习二 : 
​
#0. 准备工作:使用练习1中的emps表 
​
#1. 复制一张emps表的空表emps_back1,只有表结构,不包含任何数据 
CREATE TABLE emps_back1
AS 
SELECT * FROM emps
WHERE 1 = 2;
#2. 查询emps_back1表中的数据 
SELECT * FROM emps_back1;
#3. 创建触发器emps_del_trigger,每当向emps表中删除一条记录时,同步将删除的这条记录添加到 emps_back1表中 
DELIMITER //
CREATE TRIGGER emps_del_trigger
AFTER DELETE ON emps
FOR EACH ROW
BEGIN 
        INSERT INTO emps_back1
        VALUES(OLD.employee_id, OLD.last_name, OLD.salary);
END //
​
DELIMITER ;
​
#4. 验证触发器是否起作用
​
DELETE FROM emps
WHERE employee_id = 300;
​
SELECT * FROM emps_back1;
​
DELETE FROM emps;
​
SELECT * FROM emps_back1;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侯静川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值