I have the following trigger. It is executed after an update statement from a REST API request. My issue is that when it is sent two API calls concurrently, the trigger is executed twice, therefore a deadlock error comes up because the transaction is not finished. Is there any SQL trickery I can try? Can I check in table B if it already has B_NEEDSRECALCULATION set to '+' and bypass the update, or maybe create a trigger on tableb?
CREATE OR ALTER TRIGGER TR_TESTTRIGGER_AU FOR TABLEA
ACTIVE AFTER UPDATE POSITION 0
AS
begin
if (rdb$get_context('USER_TRANSACTION', 'WISASCRIPT') = '1') then exit;
if ((new.A_FiledB IS NULL) and
(old.A_FieldA <> new.A_FieldB) )THEN
begin
UPDATE TableB
SET B_NEEDSRECALCULATION = '+'
WHERE (B_TESTID = new.A_TESTIDFK);
end
end
Related
I have an application where user clicks a button on UI which triggers an oracle function. I want to avoid multiple parallel runs of that function in DB (at a time there should be only one ongoing run). Can I use below custom locking mechanism to achieve this without worrying about deadlock?
My Approach -
Flag will be initially set as NULL
If multiple sessions triggers the function at the same time then only one of them will continue because only one them will be able to update flag
Function will update flag back to NULL after processing is done
DDL
create table test_oracle_lock (id int, flag varchar(1), primary key (id));
Custom Locking Code to avoid parallel runs
update test_oracle_lock set flag = 'In Use' where flag is null and id = 1;
updated_rows := sql%rowcount;
commit;
IF updated_rows = 0 then --if unable to update flag (i.e. unable to acquire custom lock) then exit function
EXIT;
ELSE
--execute all sql statements to process data and update flag back to NULL
update test_oracle_lock set flag = NULL where flag = 'In Use' and id = 1;
END IF;
A better solution is select ... for update on the table. Do that at the beginning and you don't have to worry about manual locking. It will only lock the row in question, so won't interfere with other sessions and if there is a rollback, it's automatically released.
select id into l_id from my_table for update.
Yii::app()->db->createCommand("DROP TRIGGER IF EXISTS update_todo;")->execute();
Yii::app()->db->createCommand("CREATE TRIGGER update_todo AFTER DELETE ON user_todo_send FOR EACH ROW BEGIN "
. " UPDATE todo SET status = 1 WHERE id = OLD.id_todo; END;")->execute();
In response, I receive an error :
Can't update table 'todo' in stored function/trigger because it is
already used by statement which invoked this stored function/trigger..
I am going to guess that you are really using MySQL. Use an after delete trigger. Using your syntax, that would look like this (and I assume you have the right delimiter statements:
DROP TRIGGER IF EXISTS `update_todo`;
CREATE TRIGGER `update_todo` AFTER DELETE ON `user_todo_send` FOR EACH ROW
BEGIN
UPDATE `todo`
SET status = 1
WHERE id IN (SELECT OLD.id_todo
FROM user_todo_send
WHERE OLD.id_todo = todo.id
);
END;
This is really simpler to write as:
DROP TRIGGER IF EXISTS `update_todo`;
CREATE TRIGGER `update_todo` AFTER DELETE ON `user_todo_send` FOR EACH ROW
BEGIN
UPDATE `todo`
SET status = 1
WHERE id = OLD.id_todo
END;
Or, forget triggers altogether and add a cascading delete foreign key reference.
I have a ruby app. The app is doing the insert,update and delete on a particular table.
It does 2 kinds of INSERT, one insert should insert a record in the table and also into trigger_logs table. Another insert is just to insert the record into the table and do nothing. Another way to put it is, one kind of insert should log that the 'insert' happened into another table and another kind of insert should just be a normal insert. Similarly, there are 2 kinds of UPDATE and DELETE also.
I have achieved the 2 types of INSERT and UPDATE using a trigger_disable. Please refer to the trigger code below.
So, when I do a INSERT, I will set the trigger_disable boolean to true if I don't want to log the trigger. Similarly I am doing for an UPDATE too.
But I am not able to differentiate between the 2 kinds of DELETE as I do for an INSERT or UPDATE. The DELETE action is logged for both kinds of DELETE.
NOTE: I am logging all the changes that are made under a certain condition, which will be determined by the ruby app. If the condition is not satisfied, I just need to do a normal INSERT, UPDATE or DELETE accordingly.
CREATE OR REPLACE FUNCTION notify_#{#table_name}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
changed_row_id varchar(100);
BEGIN
IF TG_OP = 'DELETE' THEN
-- When the trigger is due to a delete
IF (OLD.trigger_disable IS NULL)
OR (OLD.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
-- The Problem is here: This insertion into the
-- trigger_logs table happens
-- for all the delete statements.
-- But during certain deletes I should not
-- insert into trigger_logs
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
OLD.id,
true
) RETURNING id into changed_row_id;
END IF;
RETURN OLD;
ELSE
-- The trigger is due to a Insert or Update
IF (NEW.trigger_disable IS NULL)
OR (NEW.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
NEW.id,
true
) RETURNING id into changed_row_id;
ELSE
NEW.trigger_disable := false;
END IF;
RETURN NEW;
END IF;
END
I'm going to take a stab in the dark here and guess that you're trying to contextually control whether triggers get fired.
If so, perhaps you can use a session variable?
BEGIN;
SET LOCAL myapp.fire_trigger = 'false';
DELETE FROM ...;
COMMIT;
and in your trigger, test it:
IF current_setting('myapp.fire_trigger') = 'true' THEN
Note, however, that if the setting is missing from a session you won't get NULL, you'll get an error:
regress=> SELECT current_setting('myapp.xx');
ERROR: unrecognized configuration parameter "myapp.xx"
so you'll want to:
ALTER DATABASE mydb SET myapp.fire_trigger = 'true';
Also note that the parameter is text not boolean.
Finally, there's no security on session variables. So it's not useful for security audit, since anybody can come along and just SET myapp.fire_trigger = 'false'.
(If this doesn't meet your needs, you might want to re-think whether you should be doing this with triggers at all, rather than at the application level).
How to stop from deleting a row, that has PK in another table (without FK) with a trigger?
Is CALL cannot_delete_error would stop from deleting?
This is what I've got so far.
CREATE TRIGGER T1
BEFORE DELETE ON Clients
FOR EACH ROW
BEGIN
SELECT Client, Ref FROM Clients K, Invoice F
IF F.Client = K.Ref
CALL cannot_delete_error
END IF;
END
Use an 'INSTEAD OF DELETE' trigger.
Basically, you can evaluate whether or not you should the delete the item. In the trigger you can ultimately decide to delete the item like:
--test to see if you actually should delete it.
--if you do decide to delete it
DELETE FROM MyTable
WHERE ID IN (SELECT ID FROM deleted)
One side note, remember that the 'deleted' table may be for several rows.
Another side note, try to do this outside of the db if possible! Or with a preceding query. Triggers are downright difficult to maintain. A simple query, or function (e.g. dbo.udf_CanIDeleteThis()') can be much more versatile.
If you're using MySQL 5.5 or up you can use SIGNAL
DELIMITER //
CREATE TRIGGER tg_fk_check
BEFORE DELETE ON clients
FOR EACH ROW
BEGIN
IF EXISTS(SELECT *
FROM invoices
WHERE client_id = OLD.client_id) THEN
SIGNAL sqlstate '45000'
SET message_text = 'Cannot delete a parent row: child rows exist';
END IF;
END//
DELIMITER ;
Here is SQLFiddle demo. Uncomment the last delete and click Build Schema to see it in action.
I have a calculation in my DB need to update "field1" for "table1" after the update trigger.
The problem that updating that field will cause the after update trigger to fire and execute a lengthy procedure and display errors.
please advise how to update the "field1" after the "After update" trigger has been executed and without making the "after update" trigger to execute again.
I know that I can not use NEW with After trigger.
Thanks
One can use a custom locking mechanism based on context variables which prevent from repeating invocation of AFTER UPDATE trigger.
CREATE TRIGGER au FOR table
AFTER UPDATE
POSITION 0
AS
BEGIN
IF RDB$GET_CONTEXT('USER_TRANSACTION', 'MY_LOCK') IS NULL THEN
BEGIN
RDB$SET_CONTEXT('USER_TRANSACTION', 'MY_LOCK', 1);
...
Do your update operations here
...
RDB$SET_CONTEXT('USER_TRANSACTION', 'MY_LOCK', NULL);
END
WHEN ANY DO
BEGIN
RDB$SET_CONTEXT('USER_TRANSACTION', 'MY_LOCK', NULL);
EXCEPTION;
END
END
The obvious answer is to switch to the BEFORE UPDATE trigger, as pointed out by J Cooper... however, if there is some reason you absolutely have to use AFTER UPDATE then you have to set up a flag which tells that the field needs to be recalculated and check it in your trigger. One way to do it would be to set the field to NULL in BEFORE trigger and then check against NULL in AFTER trigger, ie
CREATE TRIGGER bu_trigger BEFORE UPDATE
BEGIN
-- mark field for recalculation if needed
IF(update invalidates the field1 value)THEN
NEW.field1 = NULL;
END
CREATE TRIGGER au_trigger AFTER UPDATE
BEGIN
-- only update if the field is NULL
IF(NEW.field1 IS NULL)THEN
UPDATE table1 SET field1 = ...;
END
But using this technique you probably have to use lot of IF(OLD.field IS DISTINCT FROM NEW.field)THEN checks in triggers to avoid unnessesary updates in your triggers.
simple solution...
fire the update only if the NEW.FIELD1 value is really new, like this:
CREATE TRIGGER au FOR table1
AFTER UPDATE
POSITION 0
AS
DECLARE VARIABLE TMP AS NUMBER(15,5); -- SAME DATATYPE OF FIELD1
BEGIN
-- MAKE YOUR CALCULATION
TMP=CALCULATEDVALUE;
-- UPDATE THE ROW ONLY IF THE VALUES IS CHANGED
IF (TMP<>NEW.FIELD1) UPDATE TABLE1 SET FIELD1=:TMP WHERE KEY=NEW.KEY; -- PAY ATTENTION IF TMP OR NEW.FIELD1 CAN BE NULL. IN THIS CASE USE COALESCE OR A DIFFERENCE COMPARISON
END
Solution: Use BEFORE UPDATE TRIGGER instead of AFTER UPDATE TRIGGER
CREATE TRIGGER some_trigger BEFORE UPDATE ... etc