MySQL-进阶篇 ( 视图 + 存储过程 + 触发器 )

发布时间 2023-08-17 19:44:52作者: 朱呀朱~

MySQL-进阶篇 ( 视图 / 存储过程 / 触发器 )

视图 / 存储过程 / 触发器

视图

介绍

  • 视图 ( View ) 是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,视图中行和列的数据来自于定义视图的查询中使用的表 ( 基表 ),并且数据是在使用视图时动态生成的
  • 通俗来讲,视图只保存了查询的 SQL 逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条 SQL 查询语句上

视图大体语法

  • 创建
    • CREATE [OR REPLACE] VIEW 视图名称[(列明列表)] AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION]
      • [OR REPLACE] 可不加,除非是想替换某视图
    • create or replace view stu_v_1 as select id, name from student where id <= 10;
    • 语句中 with cascaded check option 部分在后面检查选项讲述
  • 查询
    • 查看创建视图语句:SHOW CREATE VIEW 视图名称; 所得的 Create View
    • 查看视图数据:SELECT * FROM 视图名称 ......;
      • 查看视图数据就像查表一样,后面也可以加 where 条件 ( 视图是一种虚拟存在的表 )
  • 修改
    • 方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION]
      • 此处主要在于 OR REPLACE,修改必须要加
      • SELECT语句 内做修改
      • create or replace view stu_v_1 as select id, name, no from student where id <= 10;
    • 方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH [CASCADED | LOCAL] CHECK OPTION]
      • 仍是 SELECT语句 内做修改操作
      • alter view stu_v_1 as select id, name, no from student where id <= 10;
  • 删除
    • DROP VIEW [IF EXISTS] 视图名称 [视图名称] ...
    • drop view if exists stu_v_1;

视图的检查选项

  • 实例引出

    • 也可以对视图执行插入操作 insert into stu_v_1 values(101, 'zyz');,因为视图中的数据并不在数据库中实际存在,所以实际上还是插入数据到了当初创建时的 SQL 语句中 select 用到的表 ( 基表 ) student 中

    • 若是插入数据超出视图创建时的条件范围,select 就查不到 ( 但仍然可以插入进入数据 ),所以如下语句中有了 with cascaded check option

    • create or replace view stu_v_1 as select id, name, no from student where id <= 10 with cascaded check option;

      • 这样的话,再用视图名插入超出视图创建条件的数据 ( 例如插入 id 为 100 ) 时就会报错,阻止了插入
      • 此情况下 cascaded 和 local 都可
CASCADED 和 LOCAL
  • 当使用 WITH CHECK OPTION 子句创建视图时,MySQL 会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。

    • MySQL 允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性,为了确定检查的范围,mysql 提供了两个选项:CASCADED 和 LOCAL,默认值是 CASCADED
  • CASCADED ( 级联 ):视图 v2 查询 v1 ( 依赖 v1 ) 视图进行操作 create view v2 as select id, name from v1 where id >= 10 with cascaded check option; 在查询、检查的时候不仅会要求当前条件要满足 v2 的条件,还会检查是否满足 v1 的条件

    • 即:此时再 insert 就得考虑 v1 和 v2 两个的条件
    • 若此时再创建 v3:create view v3 as select id, name from v2 where id <= 15;,因为 v3 并没有加上检查选项,所以在 insert 插入数据时就只要符合 v2 ( 创建语句中有检查选项的 ) 和 v1 ( 由于 v2 级联,所以经过 v2 就需要检查 ) 视图的条件即可
  • LOCAL:同上前提,v1 无检查,v2 依赖 v1 并 local 检查,v3 依赖 v2 并无检查

    • 则 v1 不会检查校验,v2 插入数据时会校验 v2 条件 ( 只管自己 ) 不会再将检查加到其依赖 v1 上,即 v1 没检查选项就不检查 v1 的条件

视图的更新

  • 要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
    • 聚合函数或窗口函数 ( SUM()、MIN()、MAX()、COUNT() 等 )
      • 例如基础表的两行数据由于聚合函数对应的是视图里的一行数据,那么这个视图就不能更新了
      • create view stu_v_count as select count(*) from student; 后插入语句就会报错
    • DISTINCT
    • GROUP BY
    • HAVING
    • UNION 或者 UNION ALL

作用

  • 简单
    • 视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
      • 例如一些具有复杂的条件的就可以选择创建视图使用而不用每次都再指定那些复杂的条件,操作时就针对视图即可
  • 安全
    • 数据库可以授权,但不能授权到数据库特定行和特定的列上。而通过视图用户就只能查询和修改他们所能见到的数据
      • 例如只想让班长看到班级的学生和学号,而不让其看身份证信息,就可以选择创建一个视图 ( 只有 name 与 sno 两列信息 )
  • 数据独立
    • 视图可以帮助用户屏蔽真实表结构变化带来的影响
      • 例如基表的 name 列改名为了 stuName,就可以在视图创建语句中更改 select 部分:... stuName as name ...。这样就屏蔽了基表的变化对业务的影响

案例

  • 查询每个学生所选修的课程 ( 三张表联查 ),这个功能在很多的业务中都有使用到,为了简化操作,定义一个视图
    • create view tb_stu_view as select s.name stu_name, s.no stu_no, c.name course_name from student s, student_course sc, course c where s.id = sc.studentid and sc.courseid = c.id;
    • 若是不起别名的话,视图里列名不会加上前面的表名称,所以会报两个 name 的错误,所以最好起别名显示

存储过程

介绍

  • 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的

    • 存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用
  • 通俗来讲就是,如果一个逻辑使用是:先查询,查询过后再根据结果来决定更新操作。这样的话尽管是一个逻辑功能,却要操作多次数据库,涉及多次网络请求,所以需要做优化:

    • 即将上述的查询和更新操作封装到一个集合中,这样再执行此功能时就只需要执行这个集合了

特点

  • 封装,复用
    • 如上述通俗的例子
  • 可以接收参数,也可以返回数据
    • 此点有些像一般函数的作用
  • 减少网络交互,效率提升

语法

  • 创建

    • CREATE PROCEDURE 存储过程名称([参数列表]) BEGIN -- SQL 语句 END;

    • 创建存储过程

      create procedure p3() -- 可以为无参
      begin
      	select count(*) from student;
      end;
      
      • 完成后会在与 table 同级处一个 routines 文件夹里显示

      image-20230803162239914

  • 调用

    • CALL 名称 ([参数]);

    • 调用 p3

      call p3();
      
  • 查看

    • 库下存储过程有:SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx';

    • 查看创建语句:SHOW CREATE PROCEDURE 存储过程名称;

    • information_schema 查看系统表,条件 routine_schema 指定数据库名字

      -- 查询指定数据库的存储过程及状态信息
      select * from information_schema.routines where routine_schema = 'test';
      
      -- 查询 p3 存储过程的定义语句
      show create procedure p3;
      
  • 删除

    • DROP PROCEDURE [IF EXISTS] 存储过程名称;

    • 删除 p3

      drop procedure if exists p3; -- 如果存在就删除p3
      
  • 命令行中

    • 以上语句是在数据库视图化软件工具中执行,若是在命令行中,上述创建语句创建失败 ( 因为命令行中是见到第一个分号就认为是结束,即没执行后面的 end; )

    • 此时就需要关键字 delimiter 指定 SQL 语句的结束符 ( 默认是分号 ),即:

      mysql> delimiter $$
      -- 这样命令行就改变了 SQL 语句的结束符
      
      mysql> create procedure p3()
      	-> begin
      	-> select count(*) from student;
      	-> end$$
      	
      -- 不再用的时候再改回来 
      mysql> delimiter ;
      

变量

系统变量
  • 系统变量是 MySQL 服务器提供,不是用户定义的,属于服务器层面。分为全局变量 ( GLOBAL )、会话变量 ( SESSION )

    • 全局变量就是针对于所有的会话都有效的,视图化工具中右键数据库 New 一个 Query Console 就是新建一个会话
    • 会话变量就是在当前会话窗口内才有效
  • 查看系统变量

    SHOW [SESSION|GLOBAL] VARIABLES; -- 查看所有系统变量
    
    SHOW [SESSION|GLOBAL] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方式查找变量
    show session variables like 'auto%'; -- 查询会话中自动...的变量名
    
    SELECT @@[SESSION|GLOBAL] 系统变量名;
    -- 不指定的话就是默认SESSION变量名
    select @@session .autocommit; -- 查询指定全名的变量情况
    
  • 设置系统变量

    SET [SESSION|GLOBAL] 系统变量名 = 值;
    set global autocommit = 0; -- 开关关闭(关闭后语句就要commit;手动提交了)
    
    SET @@[SESSION|GLOBAL] 系统变量名 = 值;
    
    • 改变系统变量后,若重启 mysql 服务器,autocommit 又都回到默认开启
  • 注意:

    • 如果没有指定 SESSION / GLOBAL,默认是 SESSION,会话变量
    • mysql 服务器重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置
用户定义变量
  • 用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 " @变量名 " 使用就可以。其作用域为当前连接 ( 会话 )

    • 两个 @ 就是系统变量了
  • 赋值 ( 以示区分推荐使用 ' := ' )

    -- 用SET赋值
    SET @var_name = expr [, @var_name = expr] ...;
    SET @var_name := expr [, @var_name := expr] ...;
    -- 实例:
    set @myname = 'test';
    set @myage := 20;
    set @mygender := '男', @myhobby := 'java'; -- 可一次定义多个
    select @myname, @myage, @mygender, @myhobby; -- 查看刚才定义的值
    
    -- 用SELECT赋值
    SELECT @var_name := expr [, @var_name := expr] ...;
    SELECT 字段名 INTO @var_name FROM 表名;
    -- 实例:
    select @mycolor := 'blue';
    select count(*) into @mycount from tb_user; -- 将表中统计出的结果赋值给变量@mycount
    select @mycolor, @mycount; -- 查看刚才定义的值
    
  • 使用

    -- 没赋值的话直接查询
    SELECT @var_name;
    -- 要注意若是没有任何赋值语句直接运行下面的语句:
    select @abcd; -- 有值,可以查到,只不过为null
    
    • 注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为 NULL,想用时直接使用就好
局部变量
  • 局部变量是根据需要定义的在局部生效的变量,访问之前,需要 DECLARE 声明。可用作存储过程内的局部变量和输入参数,局部变量的生效范围是在其内声明的 BEGIN ... END 块中

  • 声明:

    DECLARE 变量名 变量类型[DEFAULT ... ];
    
    • 变量类型就是数据库字段类型:INT、BIGINT、CHAR、BARCHAR、DATE、TIME 等
  • 赋值

    SET 变量名 = 值;
    SET 变量名 := 值;
    SELECT 字段名 INTO 变量名 FROM 表名 ...;
    
  • 实例使用

    -- 使用
    create procedure p2()
    begin
    	-- 声明
    	declare stu_count int default 0; -- 默认值为0,没有默认值的话 default 0 可删去
    	-- 赋值F1:
    	set stu_count := 100;
    	-- select count(*) into stu_count from student; -- F2:将查询出的结果进行赋值
    	
    	select stu_count; -- 查询方法
    end;
    
    call p2(); -- 调用执行
    

if 语句

  • if 语法:

    IF 条件1 THEN
     ... -- 条件1符合,THEN后的语法逻辑
    ELSEIF 条件2 THEN -- 可选
     ... -- 条件2符合,THEN后的语法逻辑
    ELSE			 -- 可选
     ... -- 都不符合的默认逻辑
    END IF;
    
  • 实例

    • 根据定义的分数 score 变量,判定当前分数对应的分数等级

      • score >= 85 分,等级为优秀
      • score >= 60 分,且 score < 85 分,等级为及格
      • score < 60 分,等级为不及格
    • 存储过程

      create procedure p3()
      begin
          declare score int default 58;
          declare result varchar(10);
           if score >= 85 then
               set result := '优秀';
           elseif score >= 60 then
               set result := '及格';
           else
               set result := '不及格';
           end if;
          select result;
      end;
      
      call p3;
      

参数

  • 参数类型

    类型 含义 备注
    IN 该类参数作为输入,也就是需要调用时传入值 默认
    OUT 该类参数作为输出,也就是该参数可以作为返回值
    INOUT 既可以作为输入参数,也可以作为输出参数
    • 用法:

      CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
      BEGIN
      	-- SQL语句
      END;
      
  • 实例

    • T1:根据传入的分数 score 变量,判定当前分数对应的分数等级,并返回

      • score >= 85 分,等级为优秀
      • score >= 60 分,且 score < 85 分,等级为及格
      • score < 60 分,等级为不及格
    • T2:将传入的 200 分制的分数进行换算成百分制,然后返回

    • 存储过程编写

      -- T1:
      create procedure p4(in score int, out result varchar(10))
      begin
           if score >= 85 then
               set result := '优秀';
           elseif score >= 60 then
               set result := '及格';
           else
               set result := '不及格';
           end if;
      end;
      
      -- 第二个是代表将执行的返回值返回,用一个变量接收
      -- 用@result 用户自定义变量接收
      call p4(90, @result);
      select @result;
      
      -- T2:
      create procedure p5(inout score double)
      begin
      	set score := score * 0.5;
      end;
      
      -- 调用,因为既是输出又是输入,所以也要定义一个变量
      -- 此处变量的值就是需要提前赋予即可
      set @score = 170;
      call p5(@score);
      select @score;
      

case 语句

  • 语法 1:

    CASE case_value
    	WHEN when_value1 THEN statement_list1
    	[WHEN when_value2 THEN statement_list2] ...
    	[ELSE statement_list]
    END CASE;
    
  • 语法 2:

    CASE
    	WHEN search_condition1 THEN statement_list1
    	[WHEN search_condition2 THEN statement_list2]
    	[ELSE statement_list]
    END CASE;
    
  • 实例:

    • 根据传入的月份,判定月份所属的季节

      create procedure p6(in month int)
      begin
      	declare result varchar(10);
      	
      	case
      		when month >= 1 and month <= 3 then
      			set result := '第一季度';
      		when month >= 4 and month <= 6 then
      			set result := '第二季度';
      		when month >= 7 and month <= 9 then
      			set result := '第三季度';
      		when month >= 10 and month <= 12 then
      			set result := '第四季度';
      		else
      			set result := '非法参数';
          end case;
          -- 拼接函数
          select concat('您输入的月份为:', month, ',所属季度为:', result);
      end;
      
      call p6(4);
      

三种循环语法

  • while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的 SQL 语句。具体语法为:

    #先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
    WWHILE 条件 DO
    	SQL 逻辑 ...
    END WHILE;
    
    • 实例:

      • 计算从 1 累加到 n 的值,n 为传入的参数值

        create procedure p7(in n int)
        begin
        	declare total int default 0;
        	
        	while n > 0 do
        		set total := total + n;
        		set n := n - 1;
        	end while;
        	
        	select total;
        end;
        
        -- 从1累加到10
        call p7(10);
        
  • repeat 是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:

    #先执行一次循环,然后判定逻辑是否满足,如果满足则退出。如果不满足,则继续下一次循环
    REPEAT 
    	SQL 逻辑 ...
    	UNTIL 条件
    END REPEAT;
    
    • 同上累加实例

      create procedure p8(in n int)
      begin
      	declare total int default 0;
      	
      	repeat
      		set total := total + n;
      		set n := n - 1;
      	until n <= 0
      	end repeat;
          
          select total;
      end;
      
      call p8(10);
      
  • loop

    • 实现简单的循环,如果不在 SQL 逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP 可以配合以下两个语句使用:

      [begin_label:] LOOP
      	SQL逻辑 ...
      END LOOP [end_label];
      
      LEAVE label; -- 退出指定标记的循环体
      ITERATE label; -- 跳过当前所在次循环,直接进入下一次循环
      
      • LEAVE:配合循环使用,退出循环
      • ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环
    • 实例:

      • 计算从 1 累加到 n 的值,n 为传入的参数值

      • 计算从 1 到 n 之间的偶数累加的值,n 为传入的参数值

        -- T1:
        create procedure p9(in n int)
        begin
        	declare total int default 0;
        	sum:loop -- sum为循环名
        		if n <= 0 then
        			leave sum;
        		end if;
        		
        		set total := total + n;
        		set n := n - 1;
                
        	end loop sum;
        	
        	select total;
        end;
        
        call p9(10);
        
        -- T2:
        create procedure p10(in n int)
        begin
        	declare total int default 0;
        	sum:loop -- sum为循环名
        		if n <= 0 then
        			leave sum;
        		end if;
        		
        		if n % 2 = 1 then
        			set n := n - 1;
        			iterate sum; -- 奇数跳出不计算,直接下一次循环
        		end if;
        		
        		set total := total + n;
        		set n := n - 1;
                
        	end loop sum;
        	
        	select total;
        end;
        
        call p10(10); -- 30
        

游标

  • 先前可以通过 select count(*) into stu_count from student; 将总数赋值给 stu_count,但用这种基本数据类型的不能够存储获取多条数据 ( 如:select * ... ),由此就引出了游标

  • 游标 ( CURSOR ) 是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下

    • 声明游标:DECLARE 游标名称 CURSOR FOR 查询语句;
    • 打开游标:OPEN 游标名称;
    • 获取游标记录:FETCH 游标名称 INTO 变量[, 变量];
    • 关闭游标:CLOSE 游标名称;
  • 实例:

    • 根据传入的参数 uage,来查询用户表 tb_user 中,所有的用户年龄小于等于 uage 的用户姓名 ( name ) 和专业 ( profession ),并将用户的姓名和专业插入到所创建的一张新表 ( id, name, profession ) 中

      -- 声明游标,存储查询结果集
      -- 准备:创建表结构
      -- 开启游标
      -- 获取游标中的记录
      -- 插入数据到新表中
      -- 关闭游标
      create procedure p11(in uage int)
      begin
      	-- 为了获取操作还需要接收的变量
      	declare uname varchar(100);
      	declare upro varchar(100);
      	-- 游标的声明和普通变量是有先后顺序的(游标放后)
      	declare u_cursor cursor for select name, profession from tb_user where age <= uage;
      	
      	-- 准备一个表结构来存储接收的数据
      	drop table if exists tb_user_pro;
      	create table if not exists tb_user_pro(
              id int primary key auto_increment,
              name varchar(100),
              profession varchar(100)
          );
          
          open u_cursor;
          
          -- 获取,就要遍历集合    
          while true do
          	fetch u_cursor into uname, upro; --将游标中的数存到了u...中
          	insert into tb_user_pro values (null, uname, upro); -- 拿到字段就插入到刚才创建的表中
          end while;
          
          close u_cursor;    
      end;
      
      call p11(40); -- 查年龄小于40的
      
      • 但是注意,表可以完成创建,数据也可以获取,但因为循环时只是一味插入,知道游标没有数据了,还是会执行插入操作,所有在循环过程中会报错。此处解决方式在下述条件处理程序中可得

      • 报错显示:

        image-20230804175913871

条件处理程序

  • 条件处理程序 ( Handler ) 可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:

    DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement;
    handler_action
    	CONTINUE:继续执行当前程序
    	EXIT:终止执行当前程序
    condition_value
    	SQLSTATE sqlstate_value:状态码,如:sqlstate '02000'
    	SQLWARNING:所有以01开头的SQLSTATE代码的简写
    	NOT FOUND:所有以02开头的SQLSTATE代码的简写
    	SQLEXCEPTION:所有没有被SQLWARNING或NOT FOUND 捕获的SQLSTATE代码的简写
    
  • 实例:

    • 紧接着的上一题的报错解决 ( 注意:上述报错是 [02000] 开头 )

      create procedure p11(in uage int)
      begin
      	declare uname varchar(100);
      	declare upro varchar(100);
      	-- 游标的声明和普通变量是有先后顺序的(游标放后)
      	declare u_cursor cursor for select name, profession from tb_user where age <= uage;
      	-- 声明条件处理程序放最后(因为解决的是游标没数据之后的操作,所有这里用 EXIT)
      	declare exit handler for sqlstate '02000' close u_cursor; -- 对应报错显示的02000
      	
      	drop table if exists tb_user_pro;
      	create table if not exists tb_user_pro(
              id int primary key auto_increment,
              name varchar(100),
              profession varchar(100)
          );
          
          open u_cursor;
          
          while true do
          	fetch u_cursor into uname, upro;
          	insert into tb_user_pro values (null, uname, upro);
          end while;
          
          close u_cursor;    
      end;
      
      call p11(40);
      
      • 报了错之后就会进入条件处理程序,在处理程序中识别 02000 然后执行关闭游标再 exit
      • 上述条件处理程序:declare exit handler for sqlstate '02000' close u_cursor; 还可以写为 declare exit handler for not found close u_cursor;

存储函数

  • 存储函数是有返回值的存储过程,存储函数的参数只能是 IN 类型的。具体语法如下:

    CREATE FUNCTION 存储函数名称 ([参数列表])
    RETURNS type [characteristic ... ]
    BEGIN
    	-- SQL语句
    	RETURN ...;
    END;
    
    characteristic 说明:
    DETERMINSRIC:相同的输入参数总是产生相同的结果
    NO SQL:不包含SQL语句
    READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
    
  • 实例:累计 1 到 n

    create function fun1(n int) -- 因为必须是in,所有此处可不写默认
    returns int deterministic
    begin
        declare total int default 0;
        while n > 0 do
            set total := total + n;
            set n := n - 1;
        end while;
    
        return total;
    end;
    
    select fun1(10);
    
    • 存储函数用的不多,因为存储函数的东西,存储过程也能做到,而且要求必须要有返回值,没存储过程灵活

触发器

介绍

  • 触发器是与表有关的数据库对象,指在 insert / update / delete 之前或之后,触发并执行触发器中定义的 SQL 语句集合。触发器的这种特性可以协助应用在数据库段确保数据的完整性,日志记录,数据校验等操作

  • 使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。

  • 现在对于 MySQL 来说,触发器还只支持行级触发,不支持语句级触发

    触发器类型 NEW 和 OLD
    INSERT 型触发器 NEW 表示将要或者已经新增的数据
    UPDATE 型触发器 OLD 表示修改之前的数据,NEW 表示将要或已经修改后的数据
    DELETE 型触发器 OLD 表示将要或者已经删除的数据

语法

  • 创建

    CREATE TRGGER trigger_name
    BEFORE/AFTER INSERT/UPDATE/DELETE -- 之前/之后 对应类型
    ON tbl_name FOR EACH ROW -- ON后加表名(哪一张表的数据进行...触发) 行级触发器
    BEGIN 
    	trigger_stmt; -- 具体逻辑实现
    END;
    
  • 查看

    SHOW TRIGGERS;
    
  • 删除

    DROP TRIGGER [schema_name.]trigger_name; -- 如果没有指定schema_name,默认为当前数据库.触发器名字
    

案例

  • 通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表 user_logs 中,包含增加、修改、删除

  • 增加:

    -- 准备工作:创建表结构:
    create table user_logs(
          id int(11) not null auto_increment, -- 日志表主键id
          operation varchar(20) not null comment '操作类型, insert/update/delete',
          operate_time datetime not null comment '操作时间',
          operate_id int(11) not null comment '操作的ID',
          operate_params varchar(500) comment '操作参数',
          primary key(`id`) -- 注意,此处id不是单引号' ',而是esc键下的` `
    )engine=innodb default charset=utf8;
    
    -- 定义触发器(增删改)
    create trigger tb_user_insert_trigger
        -- 插入之后记录日志即可,行级触发器
        after insert on user for each row
    begin
    -- 触发器里的具体逻辑
    -- 主要就是往日志表中插入数据
        insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES (null, 'insert', now(), new.id, concat('插入是数据内容为:id=', new.id, ',name=', new.name, ',age=', new.age, ',status=', new.status));
        -- null 指id可自增;now() 获取当前时间;new.id 拿到最新的id值
    end;
    
    -- 查看
    show triggers;
    
    -- 删除
    drop trigger tb_user_insert_trigger;
    
    -- 测试:插入数据
    insert into user(id, name, age, status) values (2, 'java', 5, '2');
    
    • user 表插入成功后,会发现 user_logs 中就多了一条日志记录:

      image-20230805110650317

  • 修改:

    -- 修改数据的触发器
    create trigger tb_user_update_trigger
        after update on user for each row
    begin
        insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES (null, 'update', now(), new.id,
             concat('更新之前的数据内容为:id=', old.id, ',name=', old.name, ',age=', old.age, ',status=', old.status,
                    ' | 更新之后的数据内容为:id=', new.id, ',name=', new.name, ',age=', new.age, ',status=', new.status));
    end;
    
    update user set age = 15 where id = 2;
    -- 若是执行更新的是多条记录:
    update user set age = 23 where id <= 2; -- 那触发器就执行两次,日志表加两行记录(行级触发器,改几行就触发几次)
    
  • 删除:

    -- 删除数据的触发器
    create trigger tb_user_delete_trigger
        after delete on user for each row
    begin
        insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
                (null, 'delete', now(), old.id,
                 concat('删除前数据为:id=', old.id, ',name=', old.name, ',age=', old.age, ',status=', old.status));
    end;
    
    delete from user where id = 2;
    
  • 记录日志:

    image-20230805161745042