Disable trigger for just one table
Question
Is it possible to disable a trigger momentarily but for just one table.
For example I have table, TableA with an on insert, update and delete trigger. I also have a table B with the same triggers but they only affect certain columns in table A.
I now have an update query that uses both tables. I know the updates in table A need to fire the triggers but the updates in table B definitely do not need to fire the triggers. So I would like to disable those triggers until the updates are done.
Is this possible? I'm using MySQL 5.1
[ADDENDUM]
Here is a trigger table A essentially
BEGIN
IF (OLD.status != 1 AND NEW.status = 2) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
ELSEIF (OLD.Status = 1 AND NEW.Status != 2) THEN
IF (NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, long, lat, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
END IF;
ELSEIF (NEW.status != 3) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL AND (NEW.geo_lat IS NULL OR NEW.geo_long IS NULL)) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
ElSEIF ((OLD.geo_lat IS NULL OR OLD.geo_long IS NULL) AND NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, longitude, latitude, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
ELSEIF (OLD.geo_lat!=NEW.geo_lat OR OLD.geo_long != NEW.geo_long OR OLD.status != NEW.status) THEN
UPDATE geo SET lat = NEW.geo_lat, long = NEW.geo_long, status = NEW.status WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
END IF;
END
Here are the triggers on table B essentially
CREATE TRIGGER `usergroups_comments_insert` AFTER INSERT ON `usergroups_comment`
FOR EACH ROW
BEGIN
CALL sp-set-comment_count(NEW.`gid`);
END;
Here is the stored procedure that's fired from table B
DELIMITER $$
CREATE PROCEDURE `sp_set-comment_count` (IN _id INT)
BEGIN
-- AC - All Count
-- OLDAC- Old All Count
DECLARE AC, OLDAC INT DEFAULT 0;
SELECT COUNT(*) AS ac
INTO AC
FROM usergroups AS ug
LEFT JOIN usergroup_comments AS ugm ON ugm.`gid` = ug.`id`
LEFT JOIN mediagallery AS dm ON ugm.mid = dm.`id`
WHERE dm.`status` NOT IN (200, 201, 202, 203, 204, 205)
AND ug.`id` = _id;
SELECT allCount
INTO OLDAC
FROM usergroups
WHERE ug.`id` = _id;
IF (OLDAC <> AC) THEN
UPDATE usergroups
SET allCount = AC,
WHERE usergroups.`id` = _id;
END IF;
END $$
Solution
Actually, if you place an if then block in every trigger, you could effectively shutdown all triggers. Here is such a code block
IF @TRIGGER_DISABLED = 0 THEN
...trigger body
END IF;
In the mysql environment, you could
- run
SET @TRIGGER_DISABLED = 1;
- do your data maintenance
- run
SET @TRIGGER_DISABLED = 0;
So your trigger for table A should look like this:
BEGIN
IF @TRIGGER_DISABLED = 0 THEN
IF (OLD.status != 1 AND NEW.status = 2) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
ELSEIF (OLD.Status = 1 AND NEW.Status != 2) THEN
IF (NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, long, lat, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
END IF;
ELSEIF (NEW.status != 3) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL AND (NEW.geo_lat IS NULL OR NEW.geo_long IS NULL)) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
ElSEIF ((OLD.geo_lat IS NULL OR OLD.geo_long IS NULL) AND NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, longitude, latitude, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
ELSEIF (OLD.geo_lat!=NEW.geo_lat OR OLD.geo_long != NEW.geo_long OR OLD.status != NEW.status) THEN
UPDATE geo SET lat = NEW.geo_lat, long = NEW.geo_long, status = NEW.status WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
END IF;
END IF;
END
So your trigger for table B should look like this:
CREATE TRIGGER `usergroups_comments_insert` AFTER INSERT ON `usergroups_comment`
FOR EACH ROW
BEGIN
IF @TRIGGER_DISABLED = 0 THEN
CALL sp-set-comment_count(NEW.`gid`);
END IF;
END;
If you want the triggers for table A to launch but not to table B, then add the code block only to table B's trigger.
OTHER TIPS
You can simplify this approach.
- Simply add a label to the first "BEGIN" block.
- Test, if the control variable is NOT NULL.
- If yes, LEAVE the trigger.
So, you can avoid to wrap the original trigger-code into an "IF"-Statment. Only the Trigger-Head must be modified in a well defined manner - which is simpler and much more reliable.
Example:
CREATE TRIGGER [YOUR_TRIGGER_SPEC]
Trigger: BEGIN
IF @TRIGGER_DISABLED NOT NULL THEN
LEAVE Trigger;
END IF;
[YOUR CODE]
END;
Have fun :-)
Edit by RolandoMySQLDBA 2012-05-01 18:38 EDT
I actually tried that months ago and it is unstable. Here is how:
Sample Table
mysql> show create table mytext\G
*************************** 1. row ***************************
Table: mytext
Create Table: CREATE TABLE `mytext` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`txt` text NOT NULL,
`txtmd5` char(32) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `txtmd5` (`txtmd5`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> select * from mytext;
+----+------------------------+----------------------------------+
| id | txt | txtmd5 |
+----+------------------------+----------------------------------+
| 1 | Rolando Edwards | ab14209a029a8f6d42d7c5a5d7a77623 |
| 2 | Pamela Edwards | 5dcbd06ea48c690032b1b29a514eb0e2 |
| 3 | Dominique Edwards | 7487431d74ac2d17a9d63123672a4bdf |
| 4 | Diamond Edwards | bc8d80541a000ed506048134058e2878 |
| 5 | The Quick Brown Fox | be94284cfa534c1837e744c061f71e17 |
| 11 | Quick Brown Fox Jumped | 495a52136057b242a80e514d7cbe77c7 |
+----+------------------------+----------------------------------+
6 rows in set (0.00 sec)
mysql>
Here is the trigger:
DROP TRIGGER IF EXISTS mytext_bi;
DELIMITER $$
CREATE TRIGGER mytext_bi BEFORE INSERT ON mytext FOR EACH ROW
TheTrigger:BEGIN
DECLARE found_count INT;
SELECT COUNT(1) INTO found_count
FROM mytext WHERE txtmd5 = MD5(LEFT(new.txt,10));
IF found_count = 1 THEN
LEAVE TheTrigger;
END IF;
SET new.txtmd5 = MD5(LEFT(new.txt,10));
END $$
DELIMITER ;
Look what happens where I try to INSERT 'Dominique Edwards'
mysql> insert into mytext (txt) values ('Dominique Edwards');
Query OK, 1 row affected (0.06 sec)
mysql> select * from mytext;
+----+------------------------+----------------------------------+
| id | txt | txtmd5 |
+----+------------------------+----------------------------------+
| 1 | Rolando Edwards | ab14209a029a8f6d42d7c5a5d7a77623 |
| 2 | Pamela Edwards | 5dcbd06ea48c690032b1b29a514eb0e2 |
| 3 | Dominique Edwards | 7487431d74ac2d17a9d63123672a4bdf |
| 4 | Diamond Edwards | bc8d80541a000ed506048134058e2878 |
| 5 | The Quick Brown Fox | be94284cfa534c1837e744c061f71e17 |
| 11 | Quick Brown Fox Jumped | 495a52136057b242a80e514d7cbe77c7 |
| 14 | Dominique Edwards | |
+----+------------------------+----------------------------------+
7 rows in set (0.00 sec)
mysql>
AHHH, IT SLIPPED IN ANYWAY !!!
I will still +1 your answer not just because of your effort but also because I tried the exact same thing (putting a label near BEGIN
) and it didn't work for me then. What a way to learn.
No one should put full faith in the MySQL Stored Procedure Language (signal processing isn't properly implemented in it) and you emphasized that when you said "Have Fun". Sometimes, having fun means try it out and see !!!. That's the true spirit of learning.
I worked around it like this:
DROP TRIGGER IF EXISTS mytext_bi;
DELIMITER $$
CREATE TRIGGER mytext_bi BEFORE INSERT ON mytext FOR EACH ROW
BEGIN
DECLARE found_count INT;
SELECT COUNT(1) INTO found_count
FROM mytext WHERE txtmd5 = MD5(LEFT(new.txt,10));
IF found_count = 0 THEN
SELECT COUNT(1) INTO found_count FROM table_that_does_not_exist;
END IF;
SET new.txtmd5 = MD5(LEFT(new.txt,10));
END $$
DELIMITER ;
The nature of stored procedures is to either go through full code navigating through IF-THEN blocks like I did in my answer or use signal processing (again, it is not fully operational)
Welcome to the DBA StackExchange !!! Again, +1 as Promised.
Sorry for the long edit, but I could not place my example code in a comment.
first of all: thanks for the '+1' :-)
And - you are right: the disabling of a trigger DOES'NT PREVENT THE ACTION HIMSELF. There is no "correct" way to do this kind of "ABORT"-Signal implemented by MySQL at the moment.
The common "workaround" is - as you described in your answer - to raise a SQL-Exception. So, the whole action will be prevented by a side effect (the exception).
But: be carefull. This "solution" creates sometimes more problems - think about transactional trigger and reqired - and hopefully controlled - rollbacks. Uuuups! (Thats, why i used the quotaion marks for this approach).
Additionally, it's a really hard stuff to debug such kinds of implementations. With this behaviour, the developer, administrator or maintainer must be able to distinct between "good" SQL-exceptions and really occurred problems. Horrible.
And last not least: this kind of limitiation hits EACH stored routines in MySQL (procedures, functions AND trigger)!
In fact: this lack of control is a blame for each provider of a RDBMS. And yepp - this is what i mean with "Have fun" :-)
Right, back to the roots - the question was: "DISABLE a trigger" (not PREVENT the action). In particular for this special requirement the approach is safe and reliable.
One more informations "behind the szene": The provided approach works safe, because MySQL initialize sessionbased variables like '@TRIGGER_DISABLED' with NULL - this is also a sideeffect.
And please hold in mind: this kind of variables are only visible to the CURRENT session! So, you can disable the trigger for YOUR current session. This kind of implementations has NO "serverwide" effects!
If you need this kind of stuff (disable trigger for the whole server), you MUST use serverwide visible and acccessable objects, like server variables (uuups - another kind of "workaround" with unpredictible side effects) or - the cleaner way - tables.
So, also "sorry" for my long response and again: have fun :-)