MySQL Trigger after update only if row has changed
Solution 1
As a workaround, you could use the timestamp (old and new) for checking though, that one is not updated when there are no changes to the row. (Possibly that is the source for confusion? Because that one is also called 'on update' but is not executed when no change occurs) Changes within one second will then not execute that part of the trigger, but in some cases that could be fine (like when you have an application that rejects fast changes anyway.)
For example, rather than
IF NEW.a <> OLD.a or NEW.b <> OLD.b /* etc, all the way to NEW.z <> OLD.z */
THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
you could use
IF NEW.ts <> OLD.ts
THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
Then you don't have to change your trigger every time you update the scheme (the issue you mentioned in the question.)
EDIT: Added full example
create table foo (a INT, b INT, ts TIMESTAMP);
create table bar (a INT, b INT);
INSERT INTO foo (a,b) VALUES(1,1);
INSERT INTO foo (a,b) VALUES(2,2);
INSERT INTO foo (a,b) VALUES(3,3);
DELIMITER ///
CREATE TRIGGER ins_sum AFTER UPDATE ON foo
FOR EACH ROW
BEGIN
IF NEW.ts <> OLD.ts THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b);
END IF;
END;
///
DELIMITER ;
select * from foo;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 1 | 1 | 2011-06-14 09:29:46 |
| 2 | 2 | 2011-06-14 09:29:46 |
| 3 | 3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
3 rows in set (0.00 sec)
-- UPDATE without change
UPDATE foo SET b = 3 WHERE a = 3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
-- the timestamo didnt change
select * from foo WHERE a = 3;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 3 | 3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
1 rows in set (0.00 sec)
-- the trigger didn't run
select * from bar;
Empty set (0.00 sec)
-- UPDATE with change
UPDATE foo SET b = 4 WHERE a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- the timestamp changed
select * from foo;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 1 | 1 | 2011-06-14 09:29:46 |
| 2 | 2 | 2011-06-14 09:29:46 |
| 3 | 4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
3 rows in set (0.00 sec)
-- and the trigger ran
select * from bar;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 3 | 4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
1 row in set (0.00 sec)
It is working because of mysql's behavior on handling timestamps. The time stamp is only updated if a change occured in the updates.
Documentation is here:
https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
desc foo;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| a | int(11) | YES | | NULL | |
| b | int(11) | YES | | NULL | |
| ts | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
Solution 2
BUT imagine a large table with changing columns. You have to compare every column and if the database changes you have to adjust the trigger. AND it doesn't "feel" good to compare every row hardcoded :)
Yeah, but that's the way to proceed.
As a side note, it's also good practice to pre-emptively check before updating:
UPDATE foo SET b = 3 WHERE a=3 and b <> 3;
In your example this would make it update (and thus overwrite) two rows instead of three.
Solution 3
I cant comment, so just beware, that if your column supports NULL values, OLD.x<>NEW.x isnt enough, because
SELECT IF(1<>NULL,1,0)
returns 0 as same as
NULL<>NULL 1<>NULL 0<>NULL 'AAA'<>NULL
So it will not track changes FROM and TO NULL
The correct way in this scenario is
((OLD.x IS NULL AND NEW.x IS NOT NULL) OR (OLD.x IS NOT NULL AND NEW.x IS NULL) OR (OLD.x<>NEW.x))
Solution 4
You can do this by comparing each field using the NULL-safe equals operator <=>
and then negating the result using NOT
.
The complete trigger would become:
DROP TRIGGER IF EXISTS `my_trigger_name`;
DELIMITER $$
CREATE TRIGGER `my_trigger_name` AFTER UPDATE ON `my_table_name` FOR EACH ROW
BEGIN
/*Add any fields you want to compare here*/
IF !(OLD.a <=> NEW.a AND OLD.b <=> NEW.b) THEN
INSERT INTO `my_other_table` (
`a`,
`b`
) VALUES (
NEW.`a`,
NEW.`b`
);
END IF;
END;$$
DELIMITER ;
(Based on a different answer of mine.)
Solution 5
In here if there any row affect with new insertion Then it will update on different table in the database.
DELIMITER $$
CREATE TRIGGER "give trigger name" AFTER INSERT ON "table name"
FOR EACH ROW
BEGIN
INSERT INTO "give table name you want to add the new insertion on previously given table" (id,name,age) VALUES (10,"sumith",24);
END;
$$
DELIMITER ;
juwens
Passionate professional and private software developer.
Updated on October 26, 2021Comments
-
juwens over 2 years
Is there any possibility to use an "after update" trigger only in the case the data has been REALLY changed. I know of "NEW and OLD". But when using them I'm only able to compare columns. For example "NEW.count <> OLD.count".
But I want something like: run trigger if "NEW <> OLD"
An Example:
create table foo (a INT, b INT); create table bar (a INT, b INT); INSERT INTO foo VALUES(1,1); INSERT INTO foo VALUES(2,2); INSERT INTO foo VALUES(3,3); CREATE TRIGGER ins_sum AFTER UPDATE ON foo FOR EACH ROW INSERT INTO bar VALUES(NEW.a, NEW.b); UPDATE foo SET b = 3 WHERE a=3; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 select * from bar; +------+------+ | a | b | +------+------+ | 3 | 3 | +------+------+
The point is, there was an update, but nothing has changed. But the trigger ran anyway. IMHO there should be a way it doesn't.
I know that I could have used
IF NOW.b <> OLD.b
for this example.
BUT imagine a large table with changing columns. You have to compare every column and if the database changes you have to adjust the trigger. AND it doesn't "feel" good to compare every column of the row hardcoded :)
Addition
As you can see on the line
Rows matched: 1 Changed: 0 Warnings: 0
MySQL knows that the line didn't change. But it doesn't share this knowledge with the trigger. A trigger like "AFTER REAL UPDATE" or something like this would be cool.
-
Johan almost 13 years@Denis, this is not needed, MySQL checks to see if the value if really changed and only starts the
UPDATE
(update+trigger) if there is a need. Your check just makes things slower. -
Denis de Bernardy almost 13 years@Johan: It is needed, and MySQL doesn't do that. It wouldn't respect the SQL standard if it did -- and the OP would not asking his question in the first place.
-
Johan almost 13 years@Denis, Yep I checked with a test trigger of my own, in 5.0 and 5.5 and it does do that. How very annoying.
-
Denis de Bernardy almost 13 yearsIt's not annoying, it's normal and actually desirable: sometimes, it's useful to have a trigger on update and to have it fire irrespective of whether changes occurred or not. The indirect consequence is it's up to the developer (or his ORM, even though the latter never do best I'm aware) to not update if no changes actually occurred.
-
Denis de Bernardy almost 13 years@Yohan: you might find this discussion interesting.
-
Denis de Bernardy almost 13 years@Johan: interesting... your removing your -1 vote revealed a bug in the SO code. I beat the 200-rep per day limit by two points, lol. :D
-
Johan almost 13 yearsMaybe we can do a loop and get infinite rep :-). Should only take a few days and some wearing out of mice to beat Jon Skeet.
-
juwens almost 13 yearsThe weird point is: MySQL seems to check if there's a difference. Because it prints the line "Rows matched: 1 Changed: 0". But it seems to ignore it. Thanks for the link. That's a neat feature of pgS.
-
juwens almost 13 yearsi don't see how this could work. Could you please explain in more detail what you mean by that.
-
Inca almost 13 years@derkommissar: I added an example
-
juwens over 10 yearsThanks for your valuable answer/hint! This makes the timestamp-solution even more atractive and the compare-solution practically unusable.
-
Lyman Zerga over 10 yearsOr you can use
COALESCE()
which returns the first of its arguments that is notNULL
. So you could write it asIF COALESCE(OLD.X,'') <> COALESCE(NEW.X,'')
-
djmj about 10 yearsOr simply use mysql null aware comparison operator
<=>
. -
Pavan Jaju about 8 yearsIs there any reference link available
-
Sir Rufo almost 8 yearsThis will not work if the update frequency is below a second. The timestamp will change (but to the same value). You have to use a timestamp(6) that should be accurate enough tracking all updates
-
Takarii almost 8 yearsSadly this doesn't answer the question. Updates don't delete rows and the OP is trying to stop a trigger triggering if the update changed no data (updating a column with a value of 1 to a value of 1 changes no data, but the update operation still runs, so the trigger does too.)
-
Hawthorne almost 8 yearsIt was my understanding that an UPDATE query will put the OLD values in the deleted recordset and the NEW values in the inserted recordset. While this will not prevent the trigger from running, it can be used to prevent the trigger from taking any actions, which is usually adequate.
-
Takarii almost 8 yearsit might be worth illustrating your answer with expanded code. However, keep in mind this question is also 5 years old.
-
Hawthorne almost 8 yearsHere's a quote from Microsoft An update transaction is similar to a delete operation followed by an insert operation; the old rows are copied to the deleted table first, and then the new rows are copied to the trigger table and to the inserted table."
-
Hawthorne almost 8 yearsSample code: CREATE TRIGGER ins_sum AFTER UPDATE ON foo INSERT INTO bar VALUES(select * from inserted except select * from deleted);
-
Wax Cage over 7 yearsAs from my answer bellow, don't forget to use <=> (null aware operator) to track changes FROM null and back TO null
-
Willem over 4 yearsPlease implement @WaxCage's comment about the null aware operator in your example, as it may cause headaches to the unaware (me ;))
-
Kapytanhook over 2 yearsthis stopped working for me in the new mariadb. new.ts is always CURRENT_TIMESTAMP even with 0 changes. anyone have any idea how to change this?