Im having some trouble with sql triggers in MySQL. The tables below are just used for testing.
First table:
CREATE TABLE `test`.`t1` (
`c1` INT NOT NULL,
`c2` VARCHAR(45) NULL,
`c3` VARCHAR(45) NULL,
PRIMARY KEY (`c1`));
Second Table:
CREATE TABLE `test`.`t2` (
`cc1` INT NOT NULL,
`cc2` VARCHAR(45) NULL,
`cc3` VARCHAR(45) NULL,
PRIMARY KEY (`cc1`));
cc1 references c1
ALl i want to do is, if the values in c2 are updated, then the corresponding field should be updated in cc2.
The trigger i came up with is:
DROP TRIGGER IF EXISTS `test`.`t1_AFTER_UPDATE`;
DELIMITER $$
USE `test`$$
CREATE DEFINER = CURRENT_USER TRIGGER `test`.`t1_AFTER_UPDATE` AFTER UPDATE ON `t1` FOR EACH ROW
BEGIN
update t2
set cc2=c2
where t2.cc1=t1.c1;
END$$
DELIMITER ;
This executes with no errors but if i try to update the values of c2,
UPDATE `test`.`t1` SET `c2` = '23' WHERE (`c1` = '11');
Operation failed: There was an error while applying the SQL script to
the database. ERROR 1054: 1054: Unknown column 't1.c1' in 'where
clause'
The update goes through if i remove the trigger.
In a Trigger, we refer to columns in the rows being updated/inserted/deleted by NEW (after operation), and OLD (before operation) keywords.
You will need to use these keywords in order to be able to update the other table.
Also, we can optimize the Trigger to initiate UPDATE operation only when there has been a change observed in the c2 value.
DROP TRIGGER IF EXISTS `test`.`t1_AFTER_UPDATE`;
DELIMITER $$
USE `test`$$
CREATE DEFINER = CURRENT_USER TRIGGER `test`.`t1_AFTER_UPDATE`
AFTER UPDATE ON `t1` FOR EACH ROW
BEGIN
-- Check if there has been any change in the c2 value or not
IF (NEW.c2 <> OLD.c2) THEN
-- Update only when there is some change
UPDATE t2
SET cc2 = NEW.c2 -- access the recent updated value of c2 using NEW keyword
WHERE cc1 = NEW.c1; -- access the c1 value of updated row using NEW keyword
END IF;
END$$
DELIMITER ;
Related
Is there any documentation (couldn't fine in https://dev.mysql.com/doc/refman/5.5/en/timestamp-initialization.html) that specifies whether an automatic update on Timestamp column or trigger executes first?
CREATE TABLE IF NOT EXISTS `table1` (
`id` VARCHAR(255) NOT NULL,
update_time TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`id`)
);
CREATE TABLE IF NOT EXISTS `table2` (
`id` VARCHAR(255) NOT NULL,
update_time TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`id`)
);
delimiter $$
DROP TRIGGER if EXISTS `trigger_upd_table1` $$
CREATE TRIGGER `trigger_upd_table1`
AFTER UPDATE ON `table1`
FOR EACH ROW BEGIN
INSERT INTO `table2` VALUES (new.`id`, new.`update_time`);
END $$
delimiter ;
INSERT INTO `table1` (`id`) VALUES ('100');
UPDATE `table1` t1
SET t1.id = '101'
WHERE t1.id = '100';
To further clarify with an example. When I run the update query for the single record in table1, will the automatic update on update_time column execute first or the trigger that is specified on table1. Currently, I am testing in v8.0.15 and seeing that automatic update on update_time column takes place first. Then the updated record is inserted into table2.
But is there any documentation that specifies the behavior?
Here's what I'm trying to do:
When there's a new INSERT into the table ACCOUNTS, I need to update the row in ACCOUNTS where pk = NEW.edit_on by setting status='E' to denote that the particular (old) account has been edited.
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` AFTER INSERT on ACCOUNTS
FOR EACH ROW BEGIN
update ACCOUNTS set status='E' where ACCOUNTS.pk = NEW.edit_on ;
END$$
DELIMITER ;
The requirement is NOT that I manipulate the newly inserted column, but an already existing column with pk = NEW.edit_on
However, I can't update the same table: Can't update table ACCOUNTS ... already used by the statement that invoked this trigger
Please suggest a workaround
PS: I have already gone through Updating table in trigger after update on the same table, Insert into same table trigger mysql, Update with after insert trigger on same table and mysql trigger with insert and update after insert on table but they dont seem to answer my question.
Edit
ACCOUNTS Table:
CREATE TABLE `ACCOUNTS` (
`pk` bigint(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(9) unsigned NOT NULL,
`edit_on` bigint(10) unsigned DEFAULT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
PRIMARY KEY (`pk`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2147483726 DEFAULT CHARSET=latin1
It seems that you can't do all this in a trigger. According to the documentation:
Within a stored function or trigger, it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
According to this answer, it seems that you should:
create a stored procedure, that inserts into/Updates the target table, then updates the other row(s), all in a transaction.
With a stored proc you'll manually commit the changes (insert and update). I haven't done this in MySQL, but this post looks like a good example.
This is how I update a row in the same table on insert
activationCode and email are rows in the table USER.
On insert I don't specify a value for activationCode, it will be created on the fly by MySQL.
Change username with your MySQL username and db_name with your db name.
CREATE DEFINER=`username`#`localhost`
TRIGGER `db_name`.`user_BEFORE_INSERT`
BEFORE INSERT ON `user`
FOR EACH ROW
BEGIN
SET new.activationCode = MD5(new.email);
END
Had the same problem but had to update a column with the id that was about to enter, so you can make an update should be done BEFORE and AFTER not BEFORE had no id so I did this trick
DELIMITER $$
DROP TRIGGER IF EXISTS `codigo_video`$$
CREATE TRIGGER `codigo_video` BEFORE INSERT ON `videos`
FOR EACH ROW BEGIN
DECLARE ultimo_id, proximo_id INT(11);
SELECT id INTO ultimo_id FROM videos ORDER BY id DESC LIMIT 1;
SET proximo_id = ultimo_id+1;
SET NEW.cassette = CONCAT(NEW.cassette, LPAD(proximo_id, 5, '0'));
END$$
DELIMITER ;
On the last entry; this is another trick:
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = ... and table_name = ...
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` **BEFORE** INSERT on ACCOUNTS
FOR EACH ROW BEGIN
SET NEW.STATUS = 'E';
END$$
DELIMITER ;
Instead you can use before insert and get max pkid for the particular table and then update the maximium pkid table record.
Im having some trouble with using the if else construct in sql triggers in MySQL. The tables below are just used for testing.
First Table:
CREATE TABLE `test2`.`t1` (
`c1` VARCHAR(5) NOT NULL,
`c2` VARCHAR(45) NULL,
`c3` VARCHAR(45) NULL,
PRIMARY KEY (`c1`));
My second table:
CREATE TABLE `test2`.`t2` (
`cc1` VARCHAR(5) NOT NULL,
`cc2` VARCHAR(45) NULL,
`cc3` VARCHAR(45) NULL,
PRIMARY KEY (`cc1`));
and the third:
CREATE TABLE `test`.`t3` (
`ccc1` VARCHAR(5) NOT NULL,
`ccc2` VARCHAR(45) NULL,
`ccc3` VARCHAR(45) NULL,
PRIMARY KEY (`cc1`));
c1 is the primary key for both cc1 and ccc1.
The trigger I am using is:
DROP TRIGGER IF EXISTS `test`.`t1_AFTER_UPDATE`;
DELIMITER $$
USE `test`$$
CREATE DEFINER=`root`#`localhost` TRIGGER `t1_AFTER_UPDATE` AFTER UPDATE ON
`t1` FOR EACH ROW
BEGIN
DECLARE start varchar(1);
set start=substring((c2),1,1);
IF(start='S') THEN
UPDATE t2
SET cc2 = NEW.c2
WHERE cc1 = NEW.c1;
else
UPDATE t3
SET ccc2 = NEW.c2
WHERE ccc1 = NEW.c1;
END IF;
END$$
DELIMITER ;
Basically, if the user updates t1.c2, then t2.cc2 OR t3.ccc2 must be updated. If t1.c2 of the NEW.c2 starts with the letter S, then t2.cc2 must be updated, else t3.ccc2 must be updated.
The query executes without a hitch but wont let me update the value of c2:
Executing:
UPDATE `test2`.`t1` SET `c2` = '24' WHERE (`c1` = 'M123');
Operation failed: There was an error while applying the SQL script to the database.
ERROR 1054: 1054: Unknown column 'c1' in 'field list'
SQL Statement:
In a Trigger, we refer to columns in the rows being updated/inserted/deleted by NEW (after operation), and OLD (before operation) keywords.
You will need to use these keywords in order to be able to access the values. You can access the c2 value from the current table using NEW keyword. You can also optimize further by doing the operation only, when the c2 value is actually modified.
DROP TRIGGER IF EXISTS `test`.`t1_AFTER_UPDATE`;
DELIMITER $$
USE `test`$$
CREATE DEFINER=`root`#`localhost` TRIGGER `t1_AFTER_UPDATE` AFTER UPDATE ON
`t1` FOR EACH ROW
BEGIN
DECLARE start varchar(1) DEFAULT NULL; -- default it to NULL
-- Check if there is any update on c2 column
IF (NEW.c2 <> OLD.c2)
-- We access the updated value of c2 using NEW keyword
SET start = SUBSTRING(NEW.c2,1,1);
END IF;
-- Do update operation on other tables only when start is not null
IF (start IS NOT NULL) THEN
IF (start = 'S') THEN
UPDATE t2
SET cc2 = NEW.c2
WHERE cc1 = NEW.c1;
ELSE
UPDATE t3
SET ccc2 = NEW.c2
WHERE ccc1 = NEW.c1;
END IF;
END IF;
END$$
DELIMITER ;
Some noteworthy points from Trigger docs:
Within the trigger body, the OLD and NEW keywords enable you to access
columns in the rows affected by a trigger. OLD and NEW are MySQL
extensions to triggers; they are not case-sensitive.
In an INSERT trigger, only NEW.col_name can be used; there is no old
row. In a DELETE trigger, only OLD.col_name can be used; there is no
new row. In an UPDATE trigger, you can use OLD.col_name to refer to
the columns of a row before it is updated and NEW.col_name to refer to
the columns of the row after it is updated.
Your trigger basically looks okay, except for the definition of start. The column reference is missing a reference to new. I am puzzled by the error message, which should be on c2 rather than c1, though.
In any case, I would simply dispense with it:
DELIMITER $$
USE `test`$$
CREATE DEFINER=`root`#`localhost` TRIGGER `t1_AFTER_UPDATE` AFTER UPDATE ON
`t1` FOR EACH ROW
BEGIN
IF (new.c2 LIKE 'S%') THEN
UPDATE t2
SET cc2 = NEW.c2
WHERE cc1 = NEW.c1;
ELSE
UPDATE t3
SET ccc2 = NEW.c2
WHERE ccc1 = NEW.c1;
END IF;
END$$
DELIMITER ;
You also need to fix the definition of t3. The primary key column does not exist.
Hi im stuck on the syntax of making this trigger to insert values to my log table. Using the sql tab inside phpmyadmin an error appears before executing the sql statement
`#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 8 `
here is my sql statement in creating a trigger
CREATE TRIGGER after_insert_list
AFTER INSERT ON list FOR EACH ROW
BEGIN
INSERT INTO log (user_id, action, date_log)
VALUES (
NEW.user_id,
NEW.action,
UNIX_TIMESTAMP()
);
END
Create your trigger like follows:
delimiter |
CREATE TRIGGER testref
BEFORE INSERT ON test1FOR EACH ROW BEGIN
INSERT INTO test2 SET a2 = NEW.a1;
DELETE FROM test3 WHERE a3 = NEW.a1;
UPDATE test4 SET b4 = b4 + 1 WHERE a4 = NEW.a1;
END;
|
delimiter ;
Here is a 100% working example:
Table demo
CREATE TABLE `demo` (
`id` int NOT NULL AUTO_INCREMENT,
`text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`updated_at` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Trigger on INSERT
DELIMITER ;;
CREATE TRIGGER `updated_at__on_insert`
BEFORE INSERT
ON `demo`
FOR EACH ROW
BEGIN
SET new.updated_at = UNIX_TIMESTAMP();
END;;
DELIMITER ;
Trigger on UPDATE
DELIMITER ;;
CREATE TRIGGER `updated_at__on_update`
BEFORE UPDATE
ON `demo`
FOR EACH ROW
BEGIN
SET new.updated_at = UNIX_TIMESTAMP();
END;;
DELIMITER ;
p.s. I was looking for such a solution for my CMS EFFCORE.
Here's what I'm trying to do:
When there's a new INSERT into the table ACCOUNTS, I need to update the row in ACCOUNTS where pk = NEW.edit_on by setting status='E' to denote that the particular (old) account has been edited.
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` AFTER INSERT on ACCOUNTS
FOR EACH ROW BEGIN
update ACCOUNTS set status='E' where ACCOUNTS.pk = NEW.edit_on ;
END$$
DELIMITER ;
The requirement is NOT that I manipulate the newly inserted column, but an already existing column with pk = NEW.edit_on
However, I can't update the same table: Can't update table ACCOUNTS ... already used by the statement that invoked this trigger
Please suggest a workaround
PS: I have already gone through Updating table in trigger after update on the same table, Insert into same table trigger mysql, Update with after insert trigger on same table and mysql trigger with insert and update after insert on table but they dont seem to answer my question.
Edit
ACCOUNTS Table:
CREATE TABLE `ACCOUNTS` (
`pk` bigint(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(9) unsigned NOT NULL,
`edit_on` bigint(10) unsigned DEFAULT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
PRIMARY KEY (`pk`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2147483726 DEFAULT CHARSET=latin1
It seems that you can't do all this in a trigger. According to the documentation:
Within a stored function or trigger, it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
According to this answer, it seems that you should:
create a stored procedure, that inserts into/Updates the target table, then updates the other row(s), all in a transaction.
With a stored proc you'll manually commit the changes (insert and update). I haven't done this in MySQL, but this post looks like a good example.
This is how I update a row in the same table on insert
activationCode and email are rows in the table USER.
On insert I don't specify a value for activationCode, it will be created on the fly by MySQL.
Change username with your MySQL username and db_name with your db name.
CREATE DEFINER=`username`#`localhost`
TRIGGER `db_name`.`user_BEFORE_INSERT`
BEFORE INSERT ON `user`
FOR EACH ROW
BEGIN
SET new.activationCode = MD5(new.email);
END
Had the same problem but had to update a column with the id that was about to enter, so you can make an update should be done BEFORE and AFTER not BEFORE had no id so I did this trick
DELIMITER $$
DROP TRIGGER IF EXISTS `codigo_video`$$
CREATE TRIGGER `codigo_video` BEFORE INSERT ON `videos`
FOR EACH ROW BEGIN
DECLARE ultimo_id, proximo_id INT(11);
SELECT id INTO ultimo_id FROM videos ORDER BY id DESC LIMIT 1;
SET proximo_id = ultimo_id+1;
SET NEW.cassette = CONCAT(NEW.cassette, LPAD(proximo_id, 5, '0'));
END$$
DELIMITER ;
On the last entry; this is another trick:
SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = ... and table_name = ...
DELIMITER $$
DROP TRIGGER IF EXISTS `setEditStatus`$$
CREATE TRIGGER `setEditStatus` **BEFORE** INSERT on ACCOUNTS
FOR EACH ROW BEGIN
SET NEW.STATUS = 'E';
END$$
DELIMITER ;
Instead you can use before insert and get max pkid for the particular table and then update the maximium pkid table record.