# 为投资表 property 实现业务约束规则 - 根据投资类别分别引用不同表的主码

# 任务描述

本关任务:
为表 property (资产表) 编写一个触发器,以实现以下完整性业务规则:

  • 如果 pro_type = 1, 则 pro_pif_id 只能引用 finances_product 表的 p_id;
  • 如果 pro_type = 2, 则 pro_pif_id 只能引用 insurance 表的 i_id;
  • 如果 pro_type = 3, 则 pro_pif_id 只能引用 fund 表的 f_id;
  • pro_type 不接受 (1,2,3) 以外的值。

各投资品种一经销售,不会再改变;

也不需考虑 finances_product,insurance,fund 的业务规则 (一经销售的理财、保险和基金产品信息会永久保存,不会被删除或修改,即使不再销售该类产品)。

# 相关知识

为了完成本关任务,你需要掌握:

  • MySQL 的流程控制编程 (参见存储过程实训);
  • 触发器的基本知识;
  • 触发器的创建;
  • 触发触发器的时机;
  • 触发触发器的事件;
  • 触发器内的特殊表。

# 触发器

触发器是与某个表绑定的命名存储对象,与存储过程一样,它由一组语句组成,当这个表上发生某个操作 (insert,delete,update) 时,触发器被触发执行。触发器一般用于实现业务完整性规则。当 primary key,foreigh key, check 等约束都无法实现某个复杂的业务规则时,可以考虑用触发器来实现。

# 触发器的创建

创建触发器的语句:

CREATE TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
trigger_body
  • trigger_nme: 每个触发器有一个唯一的命名
  • trigger_time: 触发的时机,二选一: BEFORE | AFTER
  • trigger_event: 触发事件,三选一: INSERT | UPDATE | DELETE
  • tbl_name: 与触发器绑定的表
  • trigger_body: 触发器程序体,可由变量定义、赋值,流程控制,SQL 语句等组成。但触发器体内不能使用 create,alter,drop 等 DDL 语句,也不能使用 start transaction, commit,rollback 等事务相关语句。

与创建存储过程、函数一样,创建触发器时也要用 delimiter 语句重新指定触发器定义语句的界符 (触发器内语句的分隔符仍为分号),在触发器定义之后,再把界符更改回去。

before 与 after 触发器的区别:

  • before 触发器在试图激活触发器的那条语句 (insert|delete|update) 之前执行。
  • after 触发器仅在 before 触发器 (如果有的话) 和试图激活触发器的那条语句都成功执行后才执行。
  • before 触发器或 after 触发器如果未能成功执行,则激活触发器的语句也不会执行。

# 触发器内的特殊表

在触发器内可以使用两类特殊表:

  • old 表和 new 表。它总是与触发器绑定的表有相同的结构,且只能在触发器内访问。
  • delete 触发器可以访问 old 表,其内容为被 delete 掉的数据。
  • insert 触发器可以访问 new 表,其内容为 insert 的新数据。
  • update 触发器可以访问 old 表和 new 表,old 表保存着修改前的数据,new 表保存着修改后的内容。

# 编程要求

在右侧代码文件编辑器里补充代码,实现本任务所要求的完整性业务规则。当插入的数据不符合要求时,拒绝数据的插入,并反馈出错信息:

  1. pro_type 数据不合法时,显示:
    type x is illegal!
    这里,x 系指试图插入的 pro_type 值。
  2. pro_type = 1, 但 pro_pif_id 不是 finances_product 表中的某个主码值,显示:
    finances product #x not found!
    这里,x 系指试图插入的 pro_pif_di 的值。
  3. pro_type = 2, 但 pro_pif_id 不是 insurance 表中的某个主码值,显示:
    insurance #x not found!
    这里,x 系指试图插入的 pro_pif_id 的值。
  4. pro_type = 3, 但 pro_pif_id 不是 fund 表中的某个主码值,显示:
    fund #x not found!
    这里,x 系指试图插入的 pro_pif_id 的值。

提示:

  1. 查阅 MySQL 的字符串函数,构造出错信息;
  2. 当数据不合法时,用 signal sqlstate 语句抛出异常,并设置出错信息:
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;

其中,通用 SQLSTATE '45000' 意指用户定义的待处理异常,msg 需替换成你想要显示的提示信息 (不超过 128 个字符)。

use finance1;
drop trigger if exists before_property_inserted;
-- 请在适当的地方补充代码,完成任务要求:
delimiter $$
CREATE TRIGGER before_property_inserted BEFORE INSERT ON property
FOR EACH ROW 
BEGIN
    declare tp int default new.pro_type;
    declare id int default new.pro_pif_id;
    declare msg varchar(50);
    if tp = 1 then
        if id not in (select p_id from finances_product) then
            set msg = concat("finances product #", id, " not found!");
        end if;
    elseif tp = 2 then
        if id not in (select i_id from insurance) then
            set msg = concat("insurance #", id, " not found!");
        end if;
    elseif tp = 3 then
        if id not in (select f_id from fund) then
            set msg = concat("fund #", id, " not found!");
        end if;
    else
        set msg = concat("type ", tp, " is illegal!");
    end if;
    if msg is not null then
        signal sqlstate "45000" set message_text = msg;
    end if;
END$$
 
delimiter ;

在上面的代码中, new 应该是指即将插入的这一行数据