I have an industrial system that logs alarms to a remotely hosted MySQL database. The industrial system inserts a new row whenever a property of the alarm changes (such as the time the alarm was activated, acknowledged or switched off) into a table named 'alarms'.
I don't want multiple records for each alarm, so I have set up two database triggers. The first trigger mirrors each new record to a second table, creating/updating rows as required. The second table ('alarm_display') has the 'Tag' column set as the primary key. The 'alarm' table has no primary key. The code for this trigger is:
CREATE TRIGGER `mirror_alarms` BEFORE INSERT ON `alarms`
FOR EACH ROW
INSERT INTO `alarm_display` (Tag,...,OffTime)
VALUES (new.Tag,...,new.OffTime)
ON DUPLICATE KEY UPDATE OnDate=new.OnDate,...,OffTime=new.OffTime
The second trigger should execute after the first and (ideally) delete all rows from the alarms table. (I used the Tag property of the alarm because the Tag property never changes, although I suspect I could just use a 'DELETE FROM alarms WHERE 1' statement to the same effect).
CREATE TRIGGER `remove_alarms` AFTER INSERT ON `alarms`
FOR EACH ROW DELETE FROM alarms WHERE Tag=new.Tag
My problem is that the second trigger doesn't appear to run, or if it does, the second trigger doesn't delete any rows from the database.
So here's the question: why does my second trigger not do what I expect it to do?
The explanation can be read here: http://dev.mysql.com/doc/refman/5.0/en/stored-program-restrictions.html
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.
This is your problem and your trigger ends with error #1442.
The table alarms is already being used by the statement that invoked your trigger (the insert). This essentially means you cannot modify alarms with a delete trigger.
Cheers!
Related
I have a table on a mysql 5.7 db, containing say athletes with their mean, max, avg times in a specific sport. I have another table that lists some calculated statistics based on those values.
I managed to do the calculcations that end up on the second using stored procedures. I use as input parameter to the stored procedure the athlete's name.
So when in the first table, an athlete is inserted (with his/her avg/min/max times) or his/her values are updated and I run the stored procedure, the later updates the statistics table.
My question is how to achieve the same result with triggers?
I guess it is feasible/easy to update the entire table on each insert or update of the first table. What would be more efficient performance-wise, would be on each :
INSERT into table1 values (..) where athlete_name="John Do"
(...)
ON DUPLICATE KEY UPDATE (...)
Run a trigger in the pseudocode form :
INSERT into statistics_table values (..) where athlete_name="John Do"
ON DUPLICATE KEY UPDATE (...)
How can the the athlete_name="John Do" be passed to the trigger dynamically, to avoid update the entire statistics table?
You cannot pass any parameters to a trigger and the insert statement does not support the where clause either.
Having said this, a trigger can pick up the user's name from the record being inserted / updated / deleted using NEW.athlete_name or OLD.athlete_name (whichever is required) and use that to call a stored procedure:
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.
A column named with OLD is read only. You can refer to it (if you have
the SELECT privilege), but not modify it. You can refer to a column
named with NEW if you have the SELECT privilege for it. In a BEFORE
trigger, you can also change its value with SET NEW.col_name = value
if you have the UPDATE privilege for it. This means you can use a
trigger to modify the values to be inserted into a new row or used to
update a row. (Such a SET statement has no effect in an AFTER trigger
because the row change will have already occurred.)
You can create triggers that fire after each insert or update on the parent table (athletes). Within each trigger, you can access the value of column athlete_name on the record that was just created or changed, and then invoke your stored procedure using CALL().
Here is a code sample for such an INSERT trigger :
CREATE TRIGGER athletes_upd AFTER INSERT ON athletes
FOR EACH ROW
BEGIN
CALL my_procedure(NEW.athlete_name);
END;
UPDATE trigger :
CREATE TRIGGER athletes_upd AFTER UPDATE ON athletes
FOR EACH ROW
BEGIN
CALL my_procedure(NEW.athlete_name); -- or maybe OLD.athlete_name ?
END;
I wrote a trigger that runs before a row is deleted that updates a table summarizing the data from this table. The trigger works well when I delete a single row at a time. However, if I were to delete multiple rows at once with a statement like
DELETE FROM myTable WHERE id BETWEEN 1 and 100;
Will the trigger completely run on the first row before the next row is deleted or will the triggers run all at the same time?
The trigger will completely run for every single row, see Trigger FAQ
A.5.3: Does MySQL 5.6 have statement-level or row-level triggers?
In MySQL 5.6, all triggers are FOR EACH ROW—that is, the trigger is
activated for each row that is inserted, updated, or deleted. MySQL
5.6 does not support triggers using FOR EACH STATEMENT.
That is valid for the currently newest version, MySQL 5.7 too.
from http://dev.mysql.com/doc/refman/5.5/en/trigger-syntax.html
The statement following FOR EACH ROW defines the trigger body; that is, the statement to execute each time the trigger activates, which occurs once for each row affected by the triggering event. In the example, the trigger body is a simple SET that accumulates into a user variable the values inserted into the amount column. The statement refers to the column as NEW.amount which means “the value of the amount column to be inserted into the new row.”
The delete transaction will only occur once, but then for every row affected by query the trigger will occur
I have database partially created and edited by an external CRM where certain tables have multiple (at most 2) after triggers on them. This is due to 1 trigger being auto generated by the CRM (over which I have limited control) and the other containing my code.
The CRM trigger generates the primary key for the datarow inserted. My trigger needs to access that primary key in order to write it to another table as foreign key. I use
Select #id=max(id) from mytable
since Scope_Identity did not produce the desired result somehow.
This worked until I let the CRM recreate the table and its own trigger. The maximum id of that table selected by my trigger seemed to always be actual_id - 1.
When I altered my trigger using the same code it has always had the procedure worked again.
My question is:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
And:
Is
sp_settriggerorder #triggername='mycustomtrigger', #order='Last', #stmttype='INSERT'
going to change this permanently or do I have to call that procedure again, every time the CRM recreates its trigger? (using DROP and CREATE, not ALTER)
Hope the answers to that will help someone looking at the same issue.
Regards
It's not documented, but I believe that the LAST setting will stay with a trigger, provided it is not modified. (Contrariwise, it is documented that a trigger will lose this setting if it is modified). However, it seems to work:
create table T (ID int not null)
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
create trigger T_T2 on T
after insert
as
RAISERROR('T2',10,1) WITH NOWAIT
go
create trigger T_T3 on T
after insert
as
RAISERROR('T3',10,1) WITH NOWAIT
go
insert into T(ID) values (1)
go
sp_settriggerorder 'T_T2','Last','INSERT'
go
insert into T(ID) values (2)
go
drop trigger T_T1
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
insert into T(ID) values (3)
Results:
T1
T2
T3
(1 row(s) affected)
T1
T3
T2
(1 row(s) affected)
T3
T1
T2
(1 row(s) affected)
As to your first question, however:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
It also appears to, but I would not rely on that. sp_settriggerorder is the only place where any ordering is documented.
Finally, as mentioned in my comment, I wouldn't rely on your current Select #id=max(id) from mytable method - it could be broken for a number of reasons, but the most important is that a trigger is fired once per method, and may fire in response to multiple rows, so you ought to write triggers to use the inserted pseudo-table instead (and expect it to contain 0, 1 or multiple rows).
Is there a way to create MySQL trigger which triggers on either UPDATE or INSERT?
Something like
CREATE TRIGGER t_apps_affected BEFORE INSERT OR UPDATE ...
Obviously, the above don't work. So, any workarounds without creating two separate triggers?
I need this in order to update running counter on another table.
Unfortunately, there is no shorthand form - you must create multiple triggers - one for each event.
The doc says:
trigger_event indicates the kind of statement that activates the trigger. The trigger_event can be one of the following:
INSERT: The trigger is activated whenever a new row is inserted into
the table; for example, through INSERT, LOAD DATA, and REPLACE
statements.
UPDATE: The trigger is activated whenever a row is modified; for
example, through UPDATE statements.
DELETE: The trigger is activated whenever a row is deleted from the
table; for example, through DELETE and REPLACE statements. However,
DROP TABLE and TRUNCATE TABLE statements on the table do not activate
this trigger, because they do not use DELETE. Dropping a partition
does not activate DELETE triggers, either. See Section 12.1.27,
“TRUNCATE TABLE Syntax”.
While it is impossible to put a trigger on multiple events, you can define the two triggers to merely call another stored procedure and, with that, cut down on the amount of code you need to commit. Just create the separate triggers to do nothing but, say,
CALL update_counter();
and put all of your actual work into the procedure. The triggers would then be a simple
CREATE TRIGGER t_apps_affected BEFORE INSERT ON table
FOR EACH ROW
BEGIN
CALL update_counter();
END;
I've got a number of tables that "share" a single auto-incrementing primary key - this is accomplished via a trigger on insert which looks like this:
FOR EACH ROW
BEGIN
INSERT INTO master (time) VALUES (NOW());
SET NEW.id = LAST_INSERT_ID();
END
This produces the PK for the just inserted row. This does, however, create the problem that I can't seem to figure out what that ID was. last_insert_id obviously returns nothing as the above statement wasn't executed on what's considered "the current connection".
Is there a way to access the most recently inserted row on a connection without an auto-incrementing primary key?
Update: As a temporary(?) measure I've removed the trigger and now generate the ID by making the insert to master within my model. Just seems like it would be nicer if I could somehow return the value that the trigger set.
The doc does say, "For stored functions and triggers that change the [LAST_INSERT_ID] value, the value is restored when the function or trigger ends, so following statements do not see a changed value."
Try a stored procedure, which can do your two INSERTS and return the assigned ID.
Or, give up on doing things the "Oracle way", drink the MySql Kool-Aid, and just use an auto-incrementing id on the table.