I have the following trigger to avoid updating a certain column.
ALTER TRIGGER [dbo].[MyTrigger]
ON [dbo].[MyTable]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF UPDATE(SomeID)
BEGIN
DECLARE #id INT,
#newSomeID INT,
#currentSomeID INT
SELECT #id = ID, #newSomeID = SomeID
FROM inserted
SELECT #currentSomeID = SomeID
FROM deleted
WHERE ID = #id
IF (#newSomeID <> #currentSomeID)
BEGIN
RAISERROR ('cannot change SomeID (source = [MyTrigger])', 16, 1)
ROLLBACK TRAN
END
RETURN
END
END
Since i'm selecting from inserted and deleted, will this work if someone updates the table using a where clause that encapsulates multiple rows? In other words is it possible for the inserted and deleted table to contain more than one row within the scope of my trigger?
Thanks...
why not use an instead of update trigger and just join to INSERTED and push in all the columns except the one you don't want to update? your approach does not take in account that multiple rows can be affected by an single UPDATE statement.
try something like this:
ALTER TRIGGER [dbo].[MyTrigger]
ON [dbo].[MyTable]
INSTEAD OF UPDATE
AS
BEGIN
UPDATE m
SET col1=INSERTED.col1
,col2=INSERTED.col2
,col4=INSERTED.col4
FROM [dbo].[MyTable] m
INNER JOIN INSERTED i ON m.PK=i.PK
END
you could also try something like this:
ALTER TRIGGER [dbo].[MyTrigger]
ON [dbo].[MyTable]
AFTER UPDATE
AS
BEGIN
IF EXISTS(SELECT 1 FROM INSERTED i INNER JOIN DELETED d ON i.PK=d.PK WHERE i.SomeID!=d.SomeID OR (i.SomeID IS NULL AND d.SomeID IS NOT NULL) OR (d.SomeID IS NULL AND i.SomeID IS NOT NULL))
BEGIN
RAISERROR ('cannot change SomeID (source = [MyTrigger])', 16, 1)
ROLLBACK TRAN
RETURN
END
END
This will work for multiple row updates. Also, if the "SomeID" is NOT NULL you can remove the two OR conditions in the IF EXISTS
You need to define a cursor in trigger and get all affected records in cursor and then process it.
Related
I creating triggers for several tables. The triggers have same logic. I will want to use a common stored procedure.
But I don't know how work with inserted and deleted table.
example:
SET #FiledId = (SELECT FiledId FROM inserted)
begin tran
update table with (serializable) set DateVersion = GETDATE()
where FiledId = #FiledId
if ##rowcount = 0
begin
insert table (FiledId) values (#FiledId)
end
commit tran
You can use a table valued parameter to store the inserted / deleted values from triggers, and pass it across to the proc. e.g., if all you need in your proc is the UNIQUE FileID's:
CREATE TYPE FileIds AS TABLE
(
FileId INT
);
-- Create the proc to use the type as a TVP
CREATE PROC commonProc(#FileIds AS FileIds READONLY)
AS
BEGIN
UPDATE at
SET at.DateVersion = CURRENT_TIMESTAMP
FROM ATable at
JOIN #FileIds fi
ON at.FileID = fi.FileID;
END
And then pass the inserted / deleted ids from the trigger, e.g.:
CREATE TRIGGER MyTrigger ON SomeTable FOR INSERT
AS
BEGIN
DECLARE #FileIds FileIDs;
INSERT INTO #FileIds(FileID)
SELECT DISTINCT FileID FROM INSERTED;
EXEC commonProc #FileIds;
END;
You can
select * into #Inserted from inserted
select * into #Deleted from deleted
and then
use these two temp tables in your stored proc
The tables inserted and deleted are only available inside the trigger. You can only use them in run-time. They will then contain the affected rows.
Also, your code might not work as expected if there is not exactly one row inserted.
I'm fairly new in T-SQL but i have a question concerning triggers. I have written a trigger that is used in my stored procedure witch inserts and updates data.
Now the trigger works when any row is updated in table [dbo].[users]
I don't know how to modify this trigger so that if data is inserted into [dbo].[users] table activate the trigger (this works now), but how to acheive an update scenario on the same trigger that if only a specific row is UPDATED in [dbo].[users] table that only then the trigger should be activated.
For example
If a new user is inserted and all rows are inserted in this table - activate trigger
If an old trigger is updated but only a specific field in this table is updated (working_state is the name of a column) then only should the trigger should be activated.
Source code what I have is shown here:
ALTER TRIGGER [dbo].[t_temp_triger_name]
ON [dbo].[users]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #working_state char(1),
#user_code char(11)
DECLARE zm CURSOR FOR
SELECT
working_state,
user_code
FROM
inserted
-- Added update statement that activates the trigger only when a specific -
-- column update is executed
UPDATE [dbo].[users]
SET working_state = 1
FROM [dbo].[users] U
INNER JOIN DELETED D ON U.user_code= D.user_code
WHERE U.working_state<> D.working_state
OPEN zm
FETCH NEXT FROM zm INTO #working_state, #user_code
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [FNF].[dbo].[NamedProcedure]
#working_state, #user_code
FETCH NEXT FROM zm INTO #working_state, #user_code
END
CLOSE zm
DEALLOCATE zm
END
I have came up with a solution
Added this to my code after declaration
Everything works fine.
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
DECLARE zm CURSOR FOR
SELECT
u.working_state,
u.user_code
FROM
inserted U
LEFT JOIN DELETED D ON U.user_code= D.user_code
WHERE U.working_state<> D.working_state
END
ELSE
BEGIN
DECLARE zm CURSOR FOR
SELECT
working_state,
user_code
FROM
inserted
END
I am trying to create a trigger which is triggered when an insert, delete happens in a table 'Abschaetzung_has_Varianten' and updates a table called 'Flag'. I need to select an ID from the same table to update the Flag table. Is the syntax wrong in writing the SELECT of the ID? I don't seem to get the #abschID from the select. Could anyone help me in this. Thank you.
CREATE TRIGGER trig_update_flag on [Abschaetzung_has_Varianten]
after insert, delete
As
Begin
DECLARE #x INT;
DECLARE #abschID INT;
DECLARE #value INT;
SELECT #value = 1;
SELECT #abschID = (SELECT TOP 1 Abschaetzung_ID FROM Abschaetzung_has_Varianten ORDER BY Abschaetzung_ID DESC);
SELECT #x = Count(*) FROM Flag WHERE AbschaetzID = #abschID
If #x > 0
Begin
UPDATE Flag Set [Flag] = #value WHERE AbschaetzID = #abschID;
end
end
Your code should be more like this:
CREATE TRIGGER trig_update_flag on [Abschaetzung_has_Varianten]
after insert, delete
as
begin
UPDATE Flag Set [Flag] = 1
WHERE AbschaetzID IN (SELECT DISTINCT Abschaetzung_ID FROM INSERTED)
UPDATE Flag Set [Flag] = 1
WHERE AbschaetzID IN (SELECT DISTINCT Abschaetzung_ID FROM DELETED)
end
INSERTED is a special trigger pseudo table that contains all of the updated or inserted records.
DELETED is a special trigger pseudo table that contains all of the deleted records.
This table may contain many records for one invocation of the trigger.
The code above is not the most efficient and may not suit your exact requirements but hopefully you get the idea.
I have a table that contains records that can become part of a bill. I can tell which ones are already part of a bill because the table has a BillId column that gets updated by the application code when that happens. I want to prevent updates to any record that has a non-null BillId. I'm thinking that the following should take care of that:
CREATE TRIGGER [Item_Update_AnyBilled]
ON [dbo].[Item]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #AnyBilled BIT;
SELECT TOP(1) #AnyBilled = 1
FROM inserted i
JOIN deleted d ON i.ItemId = d.ItemId
WHERE d.BillId IS NOT NULL;
IF COALESCE(#AnyBilled, 0) = 1 BEGIN
RAISERROR(2870486, 16, 1); -- Cannot update a record that is part of a bill.
ROLLBACK TRANSACTION;
END;
END;
However, there is one more wrinkle. The Item table also has a DATETIME Modified column, and a trigger that updates it.
CREATE TRIGGER [dbo].Item_Update_Modified
ON [dbo].[Item]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE a
SET Modified = getdate()
FROM Item a JOIN inserted i ON i.ItemId = a.ItemId
END
With these triggers in place, adding an Item to a Bill always causes the RAISERROR to fire. Presumably because when the BillId is populated, Item_Update_AnyBilled lets it through because the deleted.BillId is NULL, but the Item_Update_Modified then gets executed, and that secondary change causes Item_Update_AnyBilled to get executed again, and this time deleted.BillId is no longer NULL.
How can I prevent updates to the Item table except in the case where the BillId is being populated or when the only change is to the Modified column?
I'd prefer a solution that didn't require me to compare the inserted and deleted values of every column (or use COLUMNS_UPDATED()) as that would create a maintenance issue (someone would have to remember to update the trigger any time a new column is added to or deleted from the table). I am using SQL Server 2005.
Why not use an INSTEAD OF trigger? It requires a bit more work (namely a repeated UPDATE statement) but any time you can prevent work, instead of letting it happen and then rolling it back, you're going to be better off.
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS
(
SELECT 1 FROM inserted i
JOIN deleted AS d ON i.ItemId = d.ItemId
WHERE d.BillId IS NULL -- it was NULL before, may not be NULL now
)
BEGIN
UPDATE src
SET col1 = i.col1 --, ... other columns
ModifiedDate = CURRENT_TIMESTAMP -- this eliminates need for other trigger
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON i.ItemId = src.ItemId
AND (criteria to determine if at least one column has changed);
END
ELSE
BEGIN
RAISERROR(...);
END
END
GO
This doesn't fit perfectly. The criteria I've left out is left out for a reason: it can be complex to determine if a column value has changed, as it depends on the datatype, whether the column can be NULL, etc. AFAIK the built-in trigger functions can only tell if a certain column was specified, not whether the value actually changed from before.
EDIT considering that you're only concerned about the other columns that are updated due to the after trigger, I think the following INSTEAD OF trigger can replace both of your existing triggers and also deal with multiple rows updated at once (some without meeting your criteria):
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE src SET col1 = i.col1 --, ... other columns,
ModifiedDate = CURRENT_TIMESTAMP
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON src.ItemID = i.ItemID
INNER JOIN deleted AS d
ON i.ItemID = d.ItemID
WHERE d.BillID IS NULL;
IF ##ROWCOUNT = 0
BEGIN
RAISERROR(...);
END
END
GO
Hello is possible to switch between DML commands/operations (Insert,Delete,Update) on Trigger Body?, I try to snippet some T-SQL for understand me better :
CREATE TRIGGER DML_ON_TABLEA
ON TABLEA
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
CASE
WHEN (INSERT) THEN
-- INSERT ON AUX TABLEB
WHEN (DELETE) THEN
-- DELETE ON AUX TABLEB
ELSE --OR WHEN (UPDATE) THEN
-- UPDATE ON AUX TABLEB
END
END
GO
Thanks,
I will show you a simple way to check this in SQL Server 2000 or 2005 (you forgot to mention which version you are using), but in general I agree with Remus that you should break these up into separate triggers:
DECLARE #i INT, #d INT;
SELECT #i = COUNT(*) FROM inserted;
SELECT #d = COUNT(*) FROM deleted;
IF #i + #d > 0
BEGIN
IF #i > 0 AND #d = 0
BEGIN
-- logic for insert
END
IF #i > 0 AND #d > 0
BEGIN
-- logic for update
END
IF #i = 0 AND #d > 0
BEGIN
-- logic for delete
END
END
Note that this may not be perfectly forward-compatible due to the complexity MERGE introduces in SQL Server 2008. See this Connect item for more information:
MERGE can cause a trigger to fire multiple times
So if you are planning to use SQL Server 2008 and MERGE in the future, then this is even more reason to split the trigger up into a trigger for each type of DML operation.
(And if you want more reasons to avoid MERGE, read this and this.)
You can use the inserted and deleted tables to see what changes were made to the table.
For an UPDATE, the deleted table contains the old version of the row, and inserted the new version.
DELETE and INSERT use their own table as you would expect.
You can have three separate triggers, one for INSERT one for UPDATE one for DELETE. Since each trigger is different, there is no need for switch logic.
I think the general way to do this is to create a trigger for each action, like so:
CREATE TRIGGER INSERT_ON_TABLEA
ON TABLEA
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- INSERT ON AUX TABLEB
END
GO
CREATE TRIGGER DELETE_ON_TABLEA
ON TABLEA
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
-- DELETE ON AUX TABLEB
END
GO
You can use one trigger for all commands/operations by use union join;
CREATE TRIGGER DML_ON_TABLEA
ON TABLEA
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--logic for insert
insert into Backup_table (columns_name) select columns_name from inserted i
--logic for delete
UNION ALL
insert into Backup_table (columns_name) select columns_name from deleted d
END
GO
--note update command like inserted command but have another command deleted