I want to write a trigger that includes an UPDATE statement on the same table, like this:
create trigger AFTER INSERT ON myTable
FOR EACH ROW BEGIN
update myTable set ... where ...
end
How can I do this?
Quoting MySQL docs:
A trigger can also affect other tables, but 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.
You can do this (update the table that has triggered the after-insert trigger) BUT to avoid recursion, your FOR INSERT and FOR UPDATE trigger must be separate procedures. The other issue is that Microsoft SQL Server calls the FOR UPDATE trigger only once for any update statement. There is no FOR EACH ROW in SQL Server. Use a single UPDATE... table... WHERE statement inside your FOR INSERT TRIGGER to update a lot of rows at the same time.
I think it is not possible.
I create one table script is given below
CREATE TABLE `tbl1` (
`id` INT(10) NULL DEFAULT NULL,
`msg` VARCHAR(50) NULL DEFAULT NULL
)
Then i created trigger on that table using following query
delimiter |
create trigger trigger1 before insert on tbl1 for each row begin
update tbl1 set msg='insert occured in tbl1' where id=5;
end
|
delimiter ;
when i try to run following query
insert into tbl1 values (1,'ddd');
I got following error
"Cant update table 'tbl1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger"
So its not possible.
For more info visit
http://bugs.mysql.com/bug.php?id=50684
Related
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.
I created a table
Databases Name - mytrigger;
Table name - employee_audit
use mytrigger;
create table employee_audit(
id int auto_increment primary key,
employeeNumber int not null,
lastName varchar(50) not null,
changee datetime default null,
action varchar(50) default null
);
After that, I created one update trigger
My trigger name is
before_employees_update
DELIMITER $$
create trigger before_employees_update
before update on employee_audit
for each row
begin
insert into employee_audit
set action ='update',
employeeNumber = OLD.employeeNumber,
lastName = OLD.lastName,
changee = now();
end$$
DELIMITER ;
After that, I inserted values in table using this command ->
insert into employee_audit values(1,112,'prakash','2015-11-12 15:36:20' ,' ');
After that, I want to update my table row where id =1
update employee_audit set lastName = 'Sharma' where employeeNumber =112;
But it is not executed give an error
ERROR 1442 (HY000): Can't update table 'employee_audit' in stored
function/trigger because it is already used by statement which invoked
this stored function/trigger.
When I searched on Google I found a lot of Question with the same error. But not able to fix my problem. what is the reason I'm not able to update my row?
What i suggest,you can create one log table like employee_audit_LOG .
And on every insert or update in main table you can make new entry in this table or update existing record.
Also you can add updated_timestamp column to that LOG table which maintain when did specific record get updated.
The error itself tells you the answer. This is because, you can't use the same table on which trigger is being executed. You need to store your audit logs into some different table.
I have the same question as described here and also I think the answer https://stackoverflow.com/a/22343265/297487 is a good solution but I have another question about this answer.
Is the following trigger (copied from answer) thread-safe? I mean if two concurrent record inserted to table, does "priority" column (as describe in question) have consistent value (The same value as id)?
delimiter //
drop trigger if exists bi_table_name //
create trigger bi_table_name before insert on table_name
for each row begin
set #auto_id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='table_name'
AND TABLE_SCHEMA=DATABASE() );
set new.priority= #auto_id;
end;
//
delimiter ;
How do we interpret "for each row" clause?
Suppose that MySQL wants insert two concurrent rows. How does this trigger works?
One interpretation is as follows:
MySQL locks table and before insert one of the rows (one of concurrent record) MySQL trigger starts and gets current AUTO_INCREMENT value and sets it to "priority" column and then inserts a record. After that MySQL starts inserting another record and then the same situation applies for new record.
Another interpretation might be as follows:
When two concurrent records are inserted to MySQL, MySQL locks the table and then before inserting two concurrent records a trigger starts and "for each row" clause iterate between two record and set "priority" column value to the same value and then insert two concurrent record in database. In this situation the trigger does not work as expected.
Which one of the above interpretation is correct?
Update :
I have the following table :
CREATE TABLE `t_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_persian_ci NOT NULL,
`p_name` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `p_name_2` (`p_name`)
) ENGINE=InnoDB AUTO_INCREMENT=206284 DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
I want to insert value of p_name the same as id when row inserted.
The trigger that sets value of p_name is as follows(copied from your code)
delimiter $$
drop trigger if exists file_p_name $$
create trigger file_p_name before insert on t_file
for each row begin
set #id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='t_file'
AND TABLE_SCHEMA=DATABASE() );
set new.p_name= #id;
end;
$$
delimiter ;
In our application, i surround the code that inserts int_file table with try catch,in almost always everything is OK,but sometimes(in concurrent insert to t_file table), i see the following exception in our application's log:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'387456\' for key \'p_name_2\''
It seems the trigger does not work as expected or maybe i am wrong!!!
The answer referred in your question was posted by me some time back.
While in "For Each Row", The NEW is the corresponding new row, being inserted, in context. And the set new.xxx is only applicable for that row and but not 'for all rows' in batch. So there won't be any collision and the question of failure should not arise.
I have also answered a similar question
How does “for each row” work in triggers in mysql?. Please go through the examples given in the answer.
Refer to Documentation:
MySQL: Triggers
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.
We are in the process of migrating between 2 systems and need to have 2 fields for one of our database tables that always stay in sync. Here is the table structure:
CREATE TABLE `example` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`object_id` int(11) NOT NULL DEFAULT '0',
`value` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `object_id` (`object_id`)
);
Every time one of the systems inserts a new row we need to have object_id set to id. We can't use 'before insert' since the id column is an auto_increment column so it's value is NULL before insert and due to the limitations of the MySQL 'after insert' on triggers I can't do the following:
CREATE TRIGGER insert_example
AFTER INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
I can't update the code for either system so I need a way to accomplish this on the database side. Both systems are going to be inserting new rows. How can I accomplish this?
Using a trigger which fires before the insert should do the job
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
EDIT:
As the OP pointed out NEW.id won't work with auto-increment; one could use the following trigger (use at own risk):
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = (
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'example'
);
But I'd rather re-think this somewhat strange requirement - why do you need the pk value twice in the table?
Is there any reason you cant use a BEFORE INSERT trigger?
I've always seen AFTER INSERT triggers as a method to manipulate other tables rather than the table for which the trigger was executed on.
Rule of thumb, manipulate table the trigger is running on = BEFORE INSERT, manipulate other tables AFTER INSERT :)
I think your trigger will never create in the first place because you can't refer NEW.column_name in an AFTER INSERT trigger.
Try doing this in a BEFORE INSERT trigger (PLEASE IGNORE THIS FIX AS IT WILL NOT WORK):
CREATE TRIGGER `insert_example` BEFORE INSERT ON `t`
FOR EACH ROW
SET NEW.`object_id` = NEW.`id`;
Please change the table and column names as per your schema.
Hope this helps.