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.
Related
I have a table with a foreign key column referencing the same table. This is to do a grouping. I will have one group_master, and then every item in the table that is sufficiently similar to the group_master has a FK referencing that group_master id.
On delete, if the group_master is deleted, I need to update the group to have a new master so I use a Before delete trigger and in the trigger I identify the rows to be updated. Trigger and function code included below.
CREATE OR REPLACE FUNCTION assign_new_group_master()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
new_group_master details%ROWTYPE;
BEGIN
IF (SELECT COUNT(*) FROM details WHERE group_master_id = old.id AND old.id <> id) = 0
THEN RETURN old;
END IF;
SELECT * INTO new_group_master
FROM details
WHERE group_master_id = old.id AND old.id <> id
ORDER BY similarity_to_master DESC
LIMIT 1;
UPDATE details
SET group_master_id = new_group_master.id
WHERE group_master_id = old.id AND id <> old.id;
PERFORM pg_notify('group_master_id_updated'::Text,row_to_json(new_group_master)::text);
RETURN old;
END;
$$;
CREATE TRIGGER group_master_deleted_trigger
BEFORE DELETE ON details
FOR EACH ROW
WHEN (old.id = old.group_master_id)
EXECUTE FUNCTION assign_new_group_master();
This works great when deleting 1 detail element. The problem is if I try to delete many detail elements at 1, it is likely that I am deleting 2 items from the same group which causes this error to be thrown.
ERROR: tuple to be updated was already modified by an operation triggered by the current command
HINT: Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.
SQL state: 27000
As far as I understand, this is because 1 group_master gets deleted, all its children are updated, then the new group_master gets deleted and so all its children need to be updated again which is not allowed.
How can I get around this issue? I can not change to an After trigger as the hint suggests because then I would be violating the FK constraints of the group_master_id on the children.
Thanks
The way we have our error handling setup this warning will shoot an email out to everyone when it occurs (I can't change this). I really don't care that no rows were found for this job.
How do I either
A) Check if a row is present before tying to delete it or
B) Circumvent/ignore this warning somehow?
Example of the delete:
delete from schema.table
where key is null;
SQLSTATE=02000 No row error was displayed for FETCH, UPDATE, or DELETE; or the result of a query is an empty table.
I cannot insert a dummy record to delete either.
Maybe this will do
with delete_rows as (
select * from old table (delete from schema.table where key is null)
)
select count(*) from delete_rows
You may try Compound SQL (compiled) statement "eating" this warning.
BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND
BEGIN END;
DELETE ...;
END
DROP TRIGGER IF EXISTS N2Trigger
CREATE TRIGGER N2Trigger
ON dbo.Date
FOR INSERT, DELETE
AS
BEGIN
SELECT 'Inserted Datebase' as MESSAGE
SELECT 'Deleted Database' as MESSAGE
END
DELETE FROM dbo.[Date] WHERE ID = 1
Here is my code I just want when I use insert statement return 'Inserted Datebase' as MESSAGE
When I use delete statement return 'Deleted Database' as MESSAGE
The easiest way to check what action fired the trigger is to inspect the inserted and deleted pseudo-tables. If the trigger is only on DELETE/INSERT and not on update, then the logic is simply:
CREATE TRIGGER dbo.trFlarb ON dbo.flarb
FOR INSERT, DELETE
AS
BEGIN
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
SELECT 'Inserted.';
END
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
SELECT 'Deleted.';
END
END
Example db<>fiddle
Now, of course, Marc is right: triggers aren't for returning or printing output. This is just a demonstration that you can use those checks to then perform whatever logic you need to perform in the event of either action.
That said, if you have two distinctly different things you want to do depending on whether it's an insert or a delete, why not just create two separate triggers?
CREATE TRIGGER dbo.tr_I_Flarb ON dbo.flarb
FOR INSERT
AS
BEGIN
SELECT 'Inserted.';
END
GO
CREATE TRIGGER dbo.tr_D_Flarb ON dbo.flarb
FOR DELETE
AS
BEGIN
SELECT 'Deleted.';
END
GO
Note that SELECT will only "work" on your system if you haven't turned on the disallow results from triggers Server Configuration Option. Again, you should try to explain what you really want to do in the event of an insert or update, because the end goal can't be to print or return "Inserted" or "Deleted."
Unable to execute the trigger, Can anyone explain where i am doing wrong.
CREATE OR REPLACE TRIGGER HK_WS_ED_CT_T1
BEFORE INSERT OR UPDATE OF CI On HK_WS_ED_CT
FOR EACH ROW
BEGIN
IF (:new.CI = CI) THEN
UPDATE HK_WS_ED_CT
SET (TRANS_OTHER, TRANS_OTHER_DESC) =
(SELECT TRANS_OTHER, TRANS_OTHER_DESC
FROM HK_WS_ED_CT
where CI = :NEW.CI and rownum <=1 order by DATE)
end if;
END;
You are updating the table the trigger is on. This is not allowed.
You should put :new.trans_other equal to something and :new.trans_other_desc to something.
The update of the field will be ok then.
If you really want to update all the rows of that same table you should take a look at compound triggers. But I doubt this is what you want.
The main problem in DB triggers is you can't select, insert, update, delete from the table the trigger is firing on. The table is locked at this moment.
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).