什么是触发器???
简单来说,就是监视某个事件A,触发某个动作(或事件)B。
例如:当我们的订单中卖掉100个apple,则我们的商品表中的apple数量就要自动减少100.
触发器是MySQL响应insert、update、delete这3个语句而自动执行的一条MySQL语句(或位于begin end之间的一组语句)。
创建触发器有4个要素:监视事件(insert/delete/update)、监视地点(table)、触发事件(一些操作)、触发时间(Before/after).
DELIMITER $$
CREATE TRIGGER `test`.`tg_insert` AFTER INSERT -- after 为触发时间 -- insert 为监视事件 ON `test`.`student` -- student 为 监视地点 FOR EACH ROW BEGIN -- begin end中间的就是 触发事件,即要做的事情 -- select 'insert date in student table' ;这句话报错:Not allowed to return a result set from a trigger SELECT 'insert data in student table' INTO @res;
END$$ DELIMITER ;
上面这个例子的触发器的四要素如下
监视事件为:insert
监视地点:表student
触发事件:SELECT ‘insert data in student table’ INTO @res;//将此字符串保存到变量@res中
触发时间:after。
触发器不能更新,只能删除,删除命令如下:
drop trigger tg_name;
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条insert、update和delete的before和after);单一触发器不能与多个事件或多个表关联。
下面就以买卖火车票进行讲解。我们都知道,当我们下一个订单之后就表示我们将买一张(或多张)某地点的火车票,因此剩余火车票表中相应的数据就要进行一定的更新处理。否则就会出现一直看着有票但是一直买不到的情况(虽然现实生活中买火车票时就是这样,看着看着有很多票,一下子就没了)。
现在我们创建2个表,一个表为订单,一个表为火车票剩余票的表。
创建表的命令如下:
订单表如下:
create table ordersintrain( id int(10) primary key auto_increment, trainID int(10) not null, buyTicketNum int(10) not null);
火车票库存表如下:
create table ticket( trainID int(10) not null, remainderNum int(10) not null);
创建此表之后,我们为ticket添加了如下的内容:
即由3趟火车,每趟火车现在的余票为100张。
现在我们创建一个触发器,触发器的作用就是:当有人买票后,ticket的余票数要发生相应的变化。
DELIMITER $$
CREATE TRIGGER `test`.`tg_ticket` AFTER INSERT ON `test`.`ordersintrain` FOR EACH ROW BEGIN UPDATE ticket SET remainderNum=remainderNum-new.buyTicketNum WHERE trainID=new.trainID; -- new.trainID 指的是ordersintrain表中的trainID
END$$ DELIMITER ;
有了触发器之后,我们为订单表ordersintrain插入一行内容,如下:
insert into ordersintrain(trainID,buyTicketNum) values(424,5);
当执行完这条语句之后,ticket表中的结果就发生了相应的变化,如下:
这就说明,我们的触发器起了作用。
现在假设出现了一些情况,这个订单的买票的内容要发生变化,在我们的生活中比较常见,变化可能有如下两种情况:
1、退几张票,不退完。
2、撤销订单。
面对这两种情况,应该怎么来写触发器呢??
先看第一种情况
触发器的四要素如下:
监视地点:ordersintrain
监视事件:update
触发时间:after
触发事件:更新ticket表
对于update,被修改的行,修改前的数据用old 表示,old.列名引用被修改前行的数据,修改后的数据用 new 表示,new.列名引用被修改后行的数据。
触发器的创建如下:
DELIMITER $$
CREATE TRIGGER `test`.`tg_ticket_update` AFTER UPDATE ON `test`.`ordersintrain` FOR EACH ROW BEGIN UPDATE ticket SET remainderNum=remainderNum+old.buyTicketNum-new.buyTicketNum WHERE trainID=old.trainID/new.trainID; -- 先把旧的数据恢复,然后减去新的数据。
END$$ DELIMITER ;
当执行如下的操作后:
UPDATE ordersInTrain SET buyTicketNum=3 WHERE trainID=424;
里面上执行上面的操作之后,会触发tg_ticket_update
触发器,但是,实验表明:并没有触发该触发器。查找原因,原因如下:
触发事件不能这样写:
UPDATE ticket SET remainderNum=(remainderNum+old.buyTicketNum-new.buyTicketNum) WHERE trainID= old.trainID/ new.trainID ;(注意红色标注的这里) ;但是参考博客中这样写居然是Ok的,这就不能而知了,在实验过程中,是不能这样写的。
应该这样写,将一句代码分成两步来做:
UPDATE ticket SET remainderNum=(remainderNum+old.buyTicketNum) WHERE trainID=old.trainID ; -- 先加入旧的数据
UPDATE ticket SET remainderNum=(remainderNum-new.buyTicketNum) WHERE trainID=new.trainID ; -- 再减去新的数据
这种改写之后,此触发器在执行下面这条语句之后就能够被触发了,触发后ticket表中trainID=424的票数为97 张了。
UPDATE ordersInTrain SET buyTicketNum=3 WHERE trainID=424;
第二种情况:撤销订单
这种情况的触发器的四要素如下。
监视地点:ordersintrain
监视事件:delete
触发时间:after
触发事件:更新ticket表
DELIMITER $$
CREATE TRIGGER `test`.`tg_ticket_delete` AFTER DELETE ON `test`.`ordersintrain` FOR EACH ROW BEGIN UPDATE ticket SET remainderNum=remainderNum+old.buyTicketNum WHERE trainID=old.trainID;
END$$ DELIMITER ;
为方便观察结果,将上面进行的操作全部还原。即在初始状态下,每个trainID对应的车票为100,经过如下的插入语句:
INSERT INTO ordersInTrain(trainID,buyTicketNum) VALUES(424,5);
之后,触发了insert插入器,trainID=424的余票变为了95张,即少了5张。
接着执行如下的语句:
DELETE FROM ordersintrain WHERE trainID=424;
根据结果发现,trainID=424的余票变为了原来的100张。这就说明delete触发器起了作用。
上面的例子中讲解了3中触发器,分别为: (insert/update/delete)+after.除了这3种,还有如下的3中触发器:
(insert/update/delete)+before .
下面主要介绍after和before关键字的区别。
after :是先完成数据的增删改,再触发,触发的语句晚于监视的增删改操作,无法影响前面的增删改;
before :是先完成触发,再完成监视事件的增删改,即触发的语句先于监视的增删改,因此,我们就有机会对监视事件的增删改进行检查,例如,检查插入的数据是否有效(例如:一张火车票的价格不能是1000000000元呀),格式是否正确等。
还是以上面的例子为例进行讲解。
现在,ticket表中的内容如下:
还是建立一个after insert 触发器。
DELIMITER $$
CREATE TRIGGER `test`.`tg_ticket` after INSERT ON `test`.`ordersintrain` FOR EACH ROW BEGIN UPDATE ticket SET remainderNum=remainderNum-new.buyTicketNum WHERE trainID=new.trainID; -- new.trainID 指的是ordersintrain表中的trainID
END$$ DELIMITER ;
即在插入之后进行触发。当执行如下的语句时,会出现什么样的结果呢???
insert into ordersintrain(trainID,buyTicketNum) values(424,200); -- 一个买200张票的订单
从语句中可以看出,这是一个订单为200张票的订单,如果是 after insert触发器。则结果如下:
从结果可以看出,由于after insert对监视事件的插入数据没有进行有效性检查,因此出现的负数
如果创建一个before insert触发器,则可以在插入之前进行检查,避免这样的事情发生。
DELIMITER $$
CREATE TRIGGER `test`.`tg_ticket_before` BEFORE INSERT ON `test`.`ordersintrain` FOR EACH ROW BEGIN IF new.buyTicketNum>100 THEN SET new.buyTicketNum=100;
END IF ;
UPDATE ticket SET remainderNum=remainderNum-new.buyTicketNum WHERE trainID=new.trainID;
END$$ DELIMITER ;
则订单插入只会插入100,且ticket 的余票也不会变为负数。
1、《MySQL必知必会》
2、blog:http://www.cnblogs.com/zzwlovegfj/archive/2012/07/04/2576989.html