0
点赞
收藏
分享

微信扫一扫

MySQL基础(续)

田妞的读书笔记 2022-04-19 阅读 38

十一、视图

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 语句暴露在网上的风险,也提高了数据查询的安全性

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

分类

存储过程的参数类型可以是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'), 
('田馥甄','13651238755','1983-08-21'), 
('邓紫棋','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后面一定加的是标签

SELECT @num;
​
​
#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;
举报

相关推荐

0 条评论