I have an issue with a trigger on a mysql database. I have a table such as follows:
id int not null auto_increment (PK)
parent_id int not null,
rank int not null
What I'm trying to do is use a trigger to update the rank to the next highest +10 when they have the same parent_id, but this doesn't seem to be working.
DELIMITER $$
DROP TRIGGER IF EXISTS after_insert $$
create trigger after_insert
after insert on mytable
FOR EACH row
BEGIN
IF EXISTS (SELECT rank FROM mytable WHERE parent_id = new.parent_id AND id != new.id ORDER BY rank DESC LIMIT 1) THEN
UPDATE mytable SET rank = 10
WHERE id = new.id;
ELSE
UPDATE mytable SET rank = 20
WHERE id = new.id;
END IF;
END
$$
I've tried setting the new rank to a variable and calling the update statement using that, and again it didn't work. I even created another table to log what values were being selected and that worked perfectly so I can't quite understand what's going on. Is it a case of, although the trigger is "AFTER INSERT" the insert hasn't actually happened so it can't update the row it's just inserted? Another reason I ask this is, I've even tried updating the rank to different values e.g 1 and 2 depending on which statement it goes to, but it always ends up being 0.
I think you're on the right track with this thought:
Is it a case of, although the trigger is "AFTER INSERT" the insert hasn't actually happened so it can't update the row it's just inserted?
From the FAQ:
B.5.9: Can triggers access tables?
A trigger can access both old and new data in its own table. 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.
The documentation isn't clear that what you're doing won't work. OTOH, the documentation isn't clear that what you're trying to do will work either.
I think you'd be better off using a BEFORE INSERT trigger and setting NEW.rank in there. Then, the new row would have the right rank value when it is actually inserted into the table rather than patching it after. Also, you'd be able to simplify your existence check to just this:
EXISTS(SELECT rank FROM mytable WHERE parent_id = new.parent_id)
as NEW.id wouldn't have a useful value and the new row wouldn't be in the table anyway; the ORDER BY and LIMIT are also unnecessary as you're just checking if something exists so I took them out.
A BEFORE INSERT trigger seems to match your intent better anyway and that will give you correct data as soon as it is inserted into your table.
If you want the rank to be set +10 more than highest "brother's" rank, you could use:
DELIMITER $$
DROP TRIGGER IF EXISTS whatever $$
create trigger whatever
BEFORE INSERT ON mytable
FOR EACH row
BEGIN
SET NEW.rank = 10 + COALESCE(
( SELECT max(rank)
FROM mytable
WHERE parent_id = NEW.parent_id
), 0 ) ;
END
$$
Related
I'm trying to make a trigger that will update a column whenever there is an insert on another table. In my case, whenever I insert a new like in the table student_likes_post, I want the table forum_post to update its likes column accordingly. This is my query:
use mydb;
DELIMITER //
CREATE TRIGGER update_likes
after INSERT
ON student_likes_post FOR EACH ROW
BEGIN
UPDATE forum_post
SET forum_post.likes = (
select count(*)
FROM student_likes_post
WHERE student_likes_post.post_id = forum_post.id
);
END;
DELIMITER;
However, when I run it, it just keeps running forever, nothing is happening. The subquery is working though individually. I tried other triggers on the same table student_likes_post that have the same issue. Any idea how I can get this to work? Do you think is a problem with the table itself or with the code?
You probably want to update only the row in forum_post for the post that the student liked, not all the forum_posts, right?
Your trigger is currently updating all the rows in forum_post, and running the subquery once for each row in forum_post.
Here's another way to write the trigger, that updates only the single respective forum_post row:
CREATE TRIGGER update_likes
after INSERT
ON student_likes_post FOR EACH ROW
BEGIN
DECLARE like_count INT;
SELECT COUNT(*) INTO like_count
FROM student_likes_post WHERE post_id = NEW.post_id;
UPDATE forum_post
SET likes = like_count
WHERE id = NEW.post_id;
END
i have a table as below
Table name: sda_user_eform_data
ack_no Name Description
1 name1 This is name1
2 name2 This is name2
3 name3 This is name3
i have another table sda_user_eform_data_bckup which has exactly the same structure as sda_user_eform_data. I want to store only 5 rows(latest rows) in the sda_user_eform_data and whenever the ackno is greater than 5 the old values should be moved to the second(sda_user_eform_data_bckup) table.
For this first i have copied all the rows from sda_user_eform_data table to the sda_user_eform_data_bckup table. then i have created the following trigger where the i have checked the ack_no and if its greater than 5 then its deleted the oldest ack_no and insert the new value to the bckup table.
DELIMITER $$
create
trigger 'copy_eform_data' AFTER INSERT
on asdb.sda_user_eform_data
for each row begin
if (select count(s.ack_no) from asdb.sda_user_eform_data s)>5 then
delete from asdb.sda_user_eform_data where old.ack_no=(select min(s.ack_no) from asdb.sda_user_eform_data s);
insert into asdb.sda_user_eform_data_bckup select * from asdb.sda_user_eform_data where ack_no=select max(s.ack_no) from asdb.sda_user_eform_data s;
end$$
DELIMITER ;
I am not able to find out where the trigger went wrong as its not executing. Any suggestion is highly welcoming.
Thanks in advance.
That's most likely because your trigger doesn't even exist. The problem is here
create
trigger 'copy_eform_data'
With the single quotes copy_eform_data is a string.
Have a look at this post: When to use single quotes, double quotes, and backticks?
Also you should read up about the NEW and OLD keywords in triggers. Your trigger probably never matches a line.
And here
where ack_no=select max(s.ack_no) from asdb.sda_user_eform_data s
you're missing parantheses.
Apart from all that, I didn't really have a deep thought about your logic to be honest, because I don't see a point in your whole question. Why would you want to have duplicate data? I guess out of performance reasons? Have your table indexed appropriately and there should be no problem. And to get the 5 latest entries of your table simply use
FROM yourTable
ORDER BY when_was_the_entry_created_or_something DESC
LIMIT 5
You can have columns like
created timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
to use in your ORDER BY. And you probably want an index on that column.
I doing some changes in your query. Revert back if it helps.
DELIMITER $$
create
trigger 'copy_eform_data' AFTER INSERT
on asdb.sda_user_eform_data
for each row begin
insert into asdb.sda_user_eform_data_bckup
select * from asdb.sda_user_eform_data ORDER BY ack_no DESC LIMIT 1
if (select count(s.ack_no) from asdb.sda_user_eform_data s)>=5 then
delete from asdb.sda_user_eform_data;
end$$
DELIMITER ;
But make sure that when you are entering records into "asdb.sda_user_eform_data" after clearing table, "ack_no" should again start from 1
I was searching for a solution to create trigger for deleting records and came across your forum. However, I know this is over a year post and you've most likely solved the issue, but I would like to try to answer your post. I think you need a "End If" before your end $$.
So it would be:
DELIMITER $$
create
trigger 'copy_eform_data' AFTER INSERT
on asdb.sda_user_eform_data
for each row begin
if (select count(s.ack_no) from asdb.sda_user_eform_data s)>5 then
delete from asdb.sda_user_eform_data where old.ack_no=(select min(s.ack_no) from asdb.sda_user_eform_data s);
insert into asdb.sda_user_eform_data_bckup select * from asdb.sda_user_eform_data where ack_no=select max(s.ack_no) from asdb.sda_user_eform_data s;
END IF;
end$$
DELIMITER ;
MySQL doesn't currently support updating rows in the same table the trigger is assigned to since the call could become recursive. Does anyone have suggestions on a good workaround/alternative? Right now my plan is to call a stored procedure that performs the logic I really wanted in a trigger, but I'd love to hear how others have gotten around this limitation.
Edit: A little more background as requested. I have a table that stores product attribute assignments. When a new parent product record is inserted, I'd like the trigger to perform a corresponding insert in the same table for each child record. This denormalization is necessary for performance. MySQL doesn't support this and throws:
Can't update table 'mytable' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. A long discussion on the issue on the MySQL forums basically lead to: Use a stored proc, which is what I went with for now.
Thanks in advance!
You can actually up the rows in the same table as the trigger. The thread you linked to even has the solution.
For example:
TestTable ( id / lastmodified / random )
create trigger insert_lastmod
before insert on TestTable
for each row
set NEW.lastmodified = NOW();
insert into TestTable ( `random` ) values ( 'Random' );
select * from TestTable;
+----+---------------------+---------------------+
| id | lastmodified | random |
+----+---------------------+---------------------+
| 1 | 2010-12-22 14:15:23 | Random |
+----+---------------------+---------------------+
I suppose you could call the stored proc in your trigger. HOwever, if you want to update some fields in the same records that you are changing (such as an updatedby or lastupdated column) then you can do this in a beofre trigger according to the refernce manual. http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
This is a common operation for triggers and I find it difficult to believe it isn't supported.
If you want to update column that you don't read in trigger function, then as a workaround, you could put that column into separate table.
You can actually do that
The below is an example for same
DELIMITER $$
create trigger test2
before insert on ptrt
for each row
begin
if NEW.DType = "A" then
set NEW.PA = 500;
elseif NEW.DType = "B" then
set NEW.PA = 1000;
else
set NEW.PA = 0;
END IF;
END;$$
DELIMITER;
This worked for me :D
On Before / Update.
BEGIN
SET NEW.DateTimeUpdated = NOW();
END
I have created this trigger to update the seq column. I have to keep track of the order of certain items in the table, but only if the liability_category_id = 1,2. So my ordering is tricky because any item with a liability_category_id = 3 I don't need to track.
In my trigger, I'm querying to find the last entered seq number (using max(seq)), then turning around and updating the new entry with the seq + 1.
DELIMITER $$
USE `analysisdb`$$
DROP TRIGGER /*!50032 IF EXISTS */ `trigger_liability_detail_after_insert`$$
CREATE
/*!50017 DEFINER = 'admin'#'%' */
TRIGGER `trigger_liability_detail_after_insert` AFTER INSERT ON `liability_detail`
FOR EACH ROW BEGIN
DECLARE SortOrder INT;
IF NEW.liability_category_id = 1 OR NEW.liability_category_id = 2 THEN
SET SortOrder = (SELECT MAX(seq) FROM liability_detail WHERE analysis_id = new.analysis_id AND liability_category_id IN (1, 2));
UPDATE liability_detail SET seq = (SortOrder + 1) WHERE id = NEW.id;
END IF;
END;
$$
DELIMITER ;
However, when entering a new item, I get this error: Can't update table 'liability_detail' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Is there a better way to control the ordering of these items? My original thought was to simply set the first seq = 1, then seq = 2, etc. The ordering is reset for each new analysis_id though.
I think the workaround is to make this a before trigger and update the record being insert itself prior to insert.
So
CREATE
/*!50017 DEFINER = 'admin'#'%' */
TRIGGER `trigger_liability_detail_after_insert` BEFORE INSERT ON `liability_detail`
FOR EACH ROW BEGIN
DECLARE SortOrder INT;
IF NEW.liability_category_id = 1 OR NEW.liability_category_id = 2 THEN
SET NEW.seq = 1 + IFNULL((SELECT MAX(seq) FROM liability_detail WHERE analysis_id = new.analysis_id AND liability_category_id IN (1, 2)), 0);
END IF;
END;
$$
That was a quick copy/paste, but it should be something along those lines.
That's going to be tricky to handle.
The easy answer is if this could be changed to a BEFORE INSERT FOR EACH ROW trigger,
then you could:
SET NEW.seq = (SortOrder + 1);
to set the value on the row BEFORE it gets inserted into the table. But you can't do that in an AFTER INSERT FOR EACH ROW trigger.
There are some performance and concurrency concerns with using a trigger. (You don't have any guarantee that you won't be generating a "duplicate" value for the seq column when concurrent inserts are running; but that may not be a show stopper issue for you.)
I would prefer the approach of using a simple AUTO_INCREMENT column for the whole table.
The values from that would be "in order" for all the rows, so a query like
... WHERE liability_category_id = 1 ORDER BY seq
would return rows "in the order" those rows were inserted. There would be "gaps" in the sequence number for a given liability_category_id, but the sequence (order) of the inserts would be preserved.
(NOTE: MyISAM has a nifty feature of an AUTO_INCREMENT column, let's it "increment" separately for different values of a leading column in an index. But that only works in the MyISAM engine, it doesn't work in InnoDB.)
Aside from an AUTO_INCREMENT column, I would also consider a TIMESTAMP DEFAULT CURRENT_TIMESTAMP column to record the date/time when the row is inserted.
... WHERE liability_category_id = 1 ORDER BY timestamp_default_current ASC
Both of those approaches are simple column definitions, and do not require any procedural code to be written or maintained.
MySQL doesn't currently support updating rows in the same table the trigger is assigned to since the call could become recursive. Does anyone have suggestions on a good workaround/alternative? Right now my plan is to call a stored procedure that performs the logic I really wanted in a trigger, but I'd love to hear how others have gotten around this limitation.
Edit: A little more background as requested. I have a table that stores product attribute assignments. When a new parent product record is inserted, I'd like the trigger to perform a corresponding insert in the same table for each child record. This denormalization is necessary for performance. MySQL doesn't support this and throws:
Can't update table 'mytable' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. A long discussion on the issue on the MySQL forums basically lead to: Use a stored proc, which is what I went with for now.
Thanks in advance!
You can actually up the rows in the same table as the trigger. The thread you linked to even has the solution.
For example:
TestTable ( id / lastmodified / random )
create trigger insert_lastmod
before insert on TestTable
for each row
set NEW.lastmodified = NOW();
insert into TestTable ( `random` ) values ( 'Random' );
select * from TestTable;
+----+---------------------+---------------------+
| id | lastmodified | random |
+----+---------------------+---------------------+
| 1 | 2010-12-22 14:15:23 | Random |
+----+---------------------+---------------------+
I suppose you could call the stored proc in your trigger. HOwever, if you want to update some fields in the same records that you are changing (such as an updatedby or lastupdated column) then you can do this in a beofre trigger according to the refernce manual. http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
This is a common operation for triggers and I find it difficult to believe it isn't supported.
If you want to update column that you don't read in trigger function, then as a workaround, you could put that column into separate table.
You can actually do that
The below is an example for same
DELIMITER $$
create trigger test2
before insert on ptrt
for each row
begin
if NEW.DType = "A" then
set NEW.PA = 500;
elseif NEW.DType = "B" then
set NEW.PA = 1000;
else
set NEW.PA = 0;
END IF;
END;$$
DELIMITER;
This worked for me :D
On Before / Update.
BEGIN
SET NEW.DateTimeUpdated = NOW();
END