Alter a Delete Trigger to Check a Column Value - sql

I'm working with a database that has a trigger to prevent deletion of records in a certain table by raising an error and not committing the delete. I need to modify the behavior to allow the delete if a column of the record to be deleted has a specific value.
Here is the current code:
CREATE TRIGGER [dbo].[MyTable_PreventDelete]
ON [dbo].[MyTable]
INSTEAD OF DELETE
AS
-- TODO: Only run the code below if Deleted = 0
ROLLBACK
RAISERROR('ERROR: That column may not be deleted.',16,1)
RETURN
GO
I tried to simply wrap the error call in a conditional, but it appears that I can't simply reference the column of the affected row directly:
...
CREATE TRIGGER [dbo].[MyTable_PreventDelete]
ON [dbo].[MyTable]
INSTEAD OF DELETE
AS
IF IsDeleted = 0
BEGIN
ROLLBACK
RAISERROR('ERROR: That column may not be deleted.',16,1)
RETURN
END
GO

After more investigation, here's what was done. There are 3 temp tables (INSERTED, UPDATED, DELETED) referenced in each trigger for those actions. In this case, we'll check the records affected in the DELETED (which reflect the records to be affected in the batch). If any does not have the flag set, the error is raised:
IF EXISTS(SELECT 1 FROM DELETED d WHERE d.Deleted=0)
BEGIN
ROLLBACK
-- the rollback is unnecessary
RAISERROR('ERROR: That column may not be deleted.',16,1)
END
ELSE
BEGIN
DELETE c
FROM [dbo].[MyTable] c
INNER JOIN DELETED d
ON c.PrimaryKey = d.PrimaryKey
END

Related

How to use multiple triggers?

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."

Trigger to ensure each new bid is higher than previous

I have a problem with my trigger in SQL Server.
This trigger checks if the new bidding is higher then the existing ones and if not raise an error:
ALTER TRIGGER [dbo].[trg_bod_validate_Bodbedrag]
ON [dbo].[bod]
FOR INSERT, UPDATE
AS
DECLARE #v_Voorwerp numeric(25);
DECLARE #v_Bodbedrag char(6);
DECLARE #v_Max_Bodbedrag char(6);
select #v_Voorwerp = i.voorwerp, #v_Bodbedrag = i.bodbedrag
from Inserted i;
SELECT #v_Max_Bodbedrag = max(CAST(bodbedrag AS INT))
FROM bod
WHERE voorwerp = #v_Voorwerp;
IF #v_Max_Bodbedrag <= #v_Bodbedrag
BEGIN
RAISERROR ('Bod moet hoger zijn dan gegeven bod.', 16, 1)
ROLLBACK TRANSACTION
END;
ELSE
PRINT 'Row Inserted';
Now I get this error Bid amount is less then maximum, that is not acceptable', even when I insert a bidding when there aren't any existing bids.
What could be the problem?
For your knowledge: Voorwerp: Product, Bodbedrag: Amount of bid
Points to note:
In SQL Server the Inserted (and Deleted) pseudo-tables can have from 0-N rows where N is the number of rows being inserted/updated/deleted. And this has to be handled in any trigger. However as soon as you switch into set-based thinking (instead of procedural) you find its a much simpler problem anyway.
Of course we can't (easily) discriminate between rows which are OK and those which aren't, we basically have to reject the entire insert/update even if its just one row which breaks the rules.
So the join finds the max(bodbedrag) for all products which exist in Inserted - excluding the row which is being inserted/updated - because as far as we are concerned the insert/update has already taken place and that data exists in our database - until we rollback if we choose to.
I've ignored your use of a char instead of a decimal. Ideally you would correct your datatypes, but you can continue to cast/convert if you wish. I'll leave that to you.
Note we use throw these days, not raiserror.
ALTER TRIGGER [dbo].[trg_bod_validate_Bodbedrag]
ON [dbo].[bod]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Find the max of for inserted voorwerp's excluding the currently inserted/updated
-- Is an update of existing bid even allowed
IF EXISTS (
SELECT 1
FROM Inserted I
LEFT JOIN (
SELECT MAX(bodbedrag) bodbedrag
FROM dbo.dob D
INNER JOIN Inserted I ON I.voorwerp = D.voorwerp and I.ID <> D.ID
) D
WHERE I.bodbedrag < coalesce(D.bodbedrag,0)
) BEGIN
-- THROW should be used now, not RAISERROR
-- RAISERROR ('Bod moet hoger zijn dan gegeven bod.', 16, 1);
THROW 51000, 'Bod moet hoger zijn dan gegeven bod.', 1;
ROLLBACK TRANSACTION;
END; ELSE BEGIN
PRINT 'Row Inserted';
END;
END

ORA-04091 Error on "after update" trigger, how to get around?

I have a trigger set to fire after an update of a specific column. I want to update the value of a specific column after a specific value from another column changes to another specific value.
I'm getting the errors:
ORA-04091: table tableName is mutating, trigger/function may not see it
ORA-06512: at "triggerName", line 14
ORA-04088: error during execution of trigger 'triggerName'
I've rewritten this as a before update and after update, as well as tried storing the logic in a function, and using "pragma autonomous_transaction", but the error still gets thrown.
BEFORE update of columnName1 ON tableName
FOR EACH ROW
BEGIN
if :new.columnName1 = 3 AND :old.columnName1 = 1 then
update tablename
set columnName2= MOD(sequenceName.NextVal, 5) + 1
where tableName.columnName2 = :old.columnName2;
end if;
END;
/
I don't understand why the entire table is labelled as "mutating" when I am not updating the column affected by the update the trigger is responding to. Surely, you should be able to update the value of a column in a table if another value in the table changes, or am I crazy here?
Note: Only one entry would be affected at a time. In my application logic, I update the status of some person in the database. I want the database to do some logic only on a specific status change, and I want to avoid using API calls for the logic here, as you can see, it is simply one line of logic in PLSQL.
Thanks
Perhaps you don't really want to update the table but just change a value in the specific row being updated.
If so, eschew the update and just set the value:
BEFORE update of columnName1 ON tableName
FOR EACH ROW
BEGIN
if :new.columnName1 = 3 AND :old.columnName1 = 1 then
:new.columnName2 := MOD(sequenceName.NextVal, 5) + 1 ;
end if;
END;

SQL Transactions Error: Can't update table 'todo' in stored function/trigger because it is already used

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.

BEFORE DELETE trigger

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.