SQL Server 2008 created/last updated trigger issue - sql

I am using this trigger in order to fill in the CreatedDate and LastUpdated columns (of type datetime) in a table:
CREATE TRIGGER trCreatedDate ON [LasMTest]
FOR INSERT
AS
UPDATE [LasMTest]
SET [LasMTest].Created = getdate(),
[LasMTest].LastModified = getdate()
FROM [LasMTest]
INNER JOIN Inserted ON [LasMTest].[ID] = Inserted.[ID]
GO
When I check the table, the dates are off by just a fraction of a second.
Created LastModified ID
2013-03-19 09:24:32.920 2013-03-19 09:24:32.930 4
2013-03-19 09:26:39.890 2013-03-19 09:26:39.900 5
How can I modify the trigger so that they are both the exact time?

It's the interaction of your two triggers that's causing the problem.
If, instead, you set both columns to default to getdate() and ditch your insert trigger, it should work - the INSERT won't also cause an UPDATE.
The alternative is to author your INSERT trigger as an INSTEAD OF trigger that performs an INSERT rather than an UPDATE (and thus, also, avoids the UPDATE trigger firing).
If you do want to write it as an INSTEAD OF trigger, it would be something like:
CREATE TRIGGER trCreatedDate ON [LasMTest]
INSTEAD OF INSERT
AS
INSERT INTO LasMTest (/* Column List */,Created,LastModified)
SELECT /* Column List */,getdate(),getdate() from inserted
GO
INSERTs into the triggering table in an INSTEAD OF trigger don't (thankfully) cause the trigger to be fired recursively. If ID is an IDENTITY column, then it should appear in the column lists above (it hasn't been generated yet).

Try this:
CREATE TRIGGER trCreatedDate ON [LasMTest]
FOR INSERT
AS
Declare #CurrentDate Datetime
Set #CurrentDate=getdate()
UPDATE [LasMTest] SET [LasMTest].Created=#CurrentDate,[LasMTest].LastModified=#CurrentDate
FROM [LasMTest] INNER JOIN Inserted ON [LasMTest].[ID]= Inserted.[ID]
GO

You basically want to avoid the UPDATE trigger when doing the update from INSERT. This is called Nested Triggers. One easy solution is to use CONTEXT_INFO() to communicate to the nested UPDATE trigger code that you are already in an INSERT trigger so it suppresses itself:
CREATE TRIGGER trCreatedDate ON [LasMTest]
FOR INSERT
AS
SET CONTEXT_INFO 0xDEADBEEF;
UPDATE [LasMTest]
SET [LasMTest].Created = getdate(),
[LasMTest].LastModified = getdate()
FROM [LasMTest]
INNER JOIN Inserted ON [LasMTest].[ID] = Inserted.[ID];
SET CONTEXT_INFO NULL;
GO
CREATE TRIGGER trModifiedDate ON [LasMTest]
FOR UPDATE
AS
DECLARE #context varbinary(128);
SET #context = CONTEXT_INFO();
IF #context is NULL
BEGIN
UPDATE [LasMTest]
SET [LasMTest].LastModified = getdate()
FROM [LasMTest]
INNER JOIN Inserted ON [LasMTest].[ID] = Inserted.[ID];
END
GO
However such an approach is fragile. An exception can leave the context_info set and suppress all subsequent UPDATE triggers in the session. This requires adding TRY/CATCH blocks. And you always run the risk of an application using CONTEXT_INFO for its own purposes and ruining your scheme.
Another solution is to make the UPDATE trigger smart. It can check the UPDATE(Created) inside the UPDATE trigger and suppress any action if the Created column was modified. This works by convention, because you know that the only place that updates the Created column is the INSERT trigger:
CREATE TRIGGER trModifiedDate ON [LasMTest]
FOR UPDATE
AS
IF NOT UPDATE(Created)
BEGIN
UPDATE [LasMTest]
SET [LasMTest].LastModified = getdate()
FROM [LasMTest]
INNER JOIN Inserted ON [LasMTest].[ID] = Inserted.[ID];
END
GO

Related

Enable trigger after another trigger is fired

I am new to triggers and I am in a position where I have to use them. I have an azure db with two triggers on a table, one on insert, one on update.
Insert: fires when a record is inserted to a table. Copies one column to another:
CREATE TRIGGER [dbo].[tr_Set_Adjusted_StartDateTime]
ON [dbo].[Work]
AFTER INSERT
AS
BEGIN
UPDATE dbo.Work
SET [ActualStartDateTime] = [work].[StartDateTime]
FROM inserted
WHERE dbo.Work.WorkUID = inserted.WorkUID;
END
Update trigger (fires when the record gets updated):
CREATE TRIGGER [dbo].[tr_Set_Actual_EndDateTime]
ON [dbo].[Work]
AFTER UPDATE
AS
IF((Select [ActualEndDateTime] from Deleted) is null)
BEGIN
UPDATE dbo.Work
SET [ActualEndDateTime] = GETUTCDATE()
FROM deleted
WHERE dbo.Work.WorkUID = deleted.WorkUID;
END
The second trigger should only execute once: the first time the record is updated. Because the stored procedure that inserts the record doesn't populate all the columns.
The second trigger didn't originally have the IF statement. But there is an admin site that can manipulate the db and set off the update trigger.
The IF statement is now automatically firing the update trigger right away.
Is there a way to disable the update trigger if it is executed BY another trigger? Or only enable the update trigger after the record has been created?
If you have an After Update trigger on a table, it will fire each time there is an update on that table. You cannot tell trigger to Only Fire once.
But there is a way around to it. You can add a field in that table , a BIT field and SET its value to 1 in your trigger, Never manipulate this field in directly. And inside your UPDATE trigger
UPDATE w
SET [ActualEndDateTime] = GETUTCDATE()
,[Tr_Update] = 1
FROM deleted d INNER JOIN dbo.Work w ON w.WorkUID = d.WorkUID
WHERE [Tr_Update] = 0
AND [ActualEndDateTime] IS NULL
On a side note you are checking if the user hasn't put any date you want to add Current Datetime to [ActualEndDateTime] column. and since this is an after update trigger if you just execute the above statement with WHERE clause [ActualEndDateTime] IS NULL,
It would update [ActualEndDateTime] to current datetime when a row is updated for the first time and next time because [ActualEndDateTime] field would not be null it would simply filter it out anyway.
UPDATE w
SET [ActualEndDateTime] = GETUTCDATE()
FROM deleted d INNER JOIN dbo.Work w ON w.WorkUID = d.WorkUID
WHERE [ActualEndDateTime] IS NULL

Trigger preventing a column from being updated

I have created a trigger as below:
CREATE TRIGGER InsertUpdateATLastViewedMatch
ON dbo.AT_LastViewedMatch
FOR INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- update statements for trigger here
declare #id1 int, #matchFK int;
select #id1 = i.id from inserted i;
select #matchFK=i.AT_MatchFk from inserted i;
update dbo.AT_LastViewedMatch set matchId = #matchFK where id = #id1;
END
GO
While this trigger works- I am not able to update the value of the matchId column directly from SQLManager:
If I execute
UPDATE AT_LastViewedMatch set matchId='1179619' where id=5762
for example, I am still seeing the old value for matchId. I think the issue is related to the above trigger (since if I drop the trigger, it works). Is there a way to get past this?
From your trigger definition below, you have created it for insert and update
CREATE TRIGGER InsertUpdateATLastViewedMatch
ON dbo.AT_LastViewedMatch
FOR INSERT,UPDATE <-- Here
so essentially, whenever you are running an update statement from SSMS
UPDATE AT_LastViewedMatch set matchId='1179619' where id=5762
Your trigger performing the below line and setting it to old value
update dbo.AT_LastViewedMatch set matchId = #matchFK where id = #id1;
You need to remove that update from your trigger definition
CREATE TRIGGER InsertUpdateATLastViewedMatch
ON dbo.AT_LastViewedMatch
FOR INSERT

two triggers on insert of same table

Here is one very interesting problem. I am using SQL Server 2008.
I have two triggers on one common table say 'CommonTable'. one trigger is on update and other one is on insert/update/delete.
In first trigger "Trigger1", I do the checks/rollback sometime change the new inserted value based on business logic.
here is sample code
-
CREATE TRIGGER [dbo].[Trigger1] ON [dbo].[CommonTable]
FOR UPDATE
UPDATE [CommonTable]
SET
[StatusCode] = 'New Value'
WHERE
[RecId] = 'rec id value'
In second trigger "Trigger2", I store the new inserted/deleted/updated value from 'CommonTable' table to another table 'CommonTable_History' for history tracking purpose.
here is sample code
-
CREATE TRIGGER [dbo].[Trigger2] ON [dbo].[CommonTable]
FOR INSERT, UPDATE, DELETE
--based on logic read the value from DELETED or INSERTED table and store in other table.
SELECT #RowData = (SELECT * FROM DELETED AS [CommonTable] WHERE [RecId] = #RowRecId FOR XML AUTO, BINARY BASE64 , ELEMENTS)
--and then insert #RowData in 'CommonTable_History' table.
With the help of 'sp_settriggerorder', I have set the order of execution of these triggers, so first "Trigger1" get executed and then "Trigger2".
Second trigger "Trigger2" works well for insert/delete values. It works fine for new inserted value if new inserted values has not been changed by first trigger "Trigger1".
But if in some cases, inserted values has been changed in "Trigger1". say [StatusCode] = 'New Value' and old values was 'Old Value' then "Trigger2" still store the 'Old Value' instead of 'New Value'.
Why because "Trigger1" change the value but that value still has not been store in database and before that "Trigger2" get executed on Insert.
Now my requirement is, here I want to store "New Value".
So I thought, lets make "Trigger2" to use "AFTER" keywords. But "FOR" and "AFTER" behave same could not solve the problem.
Then I thought, lets make "Trigger2" to use "INSTEAD OF" keyword. But "INSTEAD OF" gives following error
"Cannot CREATE INSTEAD OF DELETE or INSTEAD OF UPDATE TRIGGER. This is because the table has a FOREIGN KEY with cascading DELETE or UPDATE."
I can not remove FOREIGN KEY with cascading DELETE or UPDATE for table 'CommonTable'.
Please let me know if you people have any other alternate solution.
-Vikram Gehlot
I think your second trigger needs to use the values from the actual table, not the inserted/deleted tables to populate the log table - inserted/deleted will always have the unaltered, original values, while your altered values will appear in the table. Make the second trigger an "After" trigger, so you will not have to use the sp_settriggerorder. Like this, for example:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[trg_Trig1]
ON [dbo].[TestTable]
FOR INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
update TestTable
set [value] = 10
where [value] = 25
END
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[trg_Trig2]
ON [dbo].[TestTable]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
insert into log_TestTable
(id, description, [value])
select tt.id, tt.description, tt.[value]
from inserted i
LEFT JOIN TestTable tt
ON tt.id = i.id
END
It may not be the cleanest solution but can you simply combine the two triggers into one? That way both pieces of SQL would know about each other's changes.
Your second trigger appears to me as if it would not work properly is mulitple records are inserted in a set-based operations unloess you use a loop which is poor choice in a trigger. Fix that first!
Instead of select * from deleted, why not join the deleted or inserted table to the original table and take the values from there (except for the id value which you get from deleted or inserted, that should give you the most current values of all fileds and if you add other trigger logic later wil not break.

How to update a single table using trigger in MS SQL 2008

I have a table PeroidicDeduction and the fields are ID(auto-increment),TotalDeduction(e.g.it can be loan),Paid(on which the deduction for each month),RemainingAmount,
What I want is when every time I insert or update the table---RemainingAmount will get the value of TotalDeduction-SUM(Paid)....and writ the following trigger...but dosen't work for me
CREATE TRIGGER dbo.UpdatePD
ON PeroidicDedcution
AFTER INSERT,UPDATE
AS
BEGIN
UPDATE PeroidicDedcution
SET REmaininAmoubnt=(SELECT TotalDeduction-(SELECT SUM(Paid) FROM PeroidicDeduction) FROM PeroidicDeduction)
END
NOTE: it is on a Single table
Create two triggers, an INSTEAD OF UPDATE and INSTEAD OF INSERT. Here is the code for the INSTEAD OF UPDATE:
CREATE TRIGGER dbo.UpdatePD ON PeroidicDedcution
INSTEAD OF Update
AS
SET NOCOUNT ON
UPDATE p
SET col1=i.col1
,col2=i.col2
FROM INSERTED i
INNER JOIN PeroidicDedcution p ON i.PK=p.PK
UPDATE PeroidicDedcution
SET REmaininAmoubnt=(SELECT TotalDeduction-(SELECT SUM(Paid) FROM PeroidicDeduction) FROM PeroidicDeduction)
go
It will do both the original update that fires the trigger, as well as the SUM logic from the trigger in the question.
here is the INSTEAD OF INSERT trigger:
CREATE TRIGGER dbo.InsertPD ON PeroidicDedcution
INSTEAD OF INSERT
AS
SET NOCOUNT ON
INSERT INTO PeroidicDedcution
(col1, col2, ...)
SELECT
col1, col2, ...
FROM INSERTED
UPDATE PeroidicDedcution
SET REmaininAmoubnt=(SELECT TotalDeduction-(SELECT SUM(Paid) FROM PeroidicDeduction) FROM PeroidicDeduction)
go
You should not use a trigger to do this as it will recursively fire itself if you have an update trigger on a table and the trigger causes and update to the same table. rather add the logic to your insert and update stored procedures

After insert, update trigger not running

I have two triggers After Insert or Update and Instead of Insert. It appears that the after trigger is not running or sending the correct data.
I have verified the correct operation of Z_UpdateStageTable stored procedure and the Instead of Insert trigger. Removing the Instead of Insert trigger doesn't have any affect. The After Insert, Update trigger was working correctly at one time, I haven't made any changes to it. I have tried deleting it and adding it, but it still doesn't run or have the correct data.
Any Ideas?
Instead of Insert:
ALTER TRIGGER [DeleteExistingFilter]
ON [dbo].[Z_MobileSyncFilters]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM Z_MobileSyncFilters WHERE UserID = (SELECT UserID FROM INSERTED);
INSERT INTO Z_MobileSyncFilters
SELECT *
FROM INSERTED;
END
After Insert, Update:
TRIGGER [UpdateStageTable]
ON [dbo].[Z_MobileSyncFilters]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #AllWos AS VARCHAR(5000);
DECLARE #PmWos AS VARCHAR(5000);
DECLARE #RepWos AS VARCHAR(5000);
SET #AllWos = (SELECT AllWos FROM INSERTED);
SET #RepWos = (SELECT AllWos FROM INSERTED);
SET #PmWos = (SELECT AllWos FROM INSERTED);
EXEC Z_UpdateStageTable #AllWos;
EXEC Z_UpdateStageTable #RepWos;
EXEC Z_UpdateStageTable #PmWos;
END
Is there a typo in the SET part of the AFTER trigger? You're selecting the same thing into three different variables.
Rather than confirming the behavior of Z_UpdateStageTable, I'd try to replace it with something dirt simple (a parameterless sql statement, say) to test whether the trigger's being called. It's possible that the sproc's not being called with what you think it's being called with.
You can add PRINT statements to the trigger and manually insert from ManagementStudio/Enterprise Manager to see where the trigger fails.
I see a problem when you insert multiple records in a single statement, as the SELECT FROM Inserted will return more than 1 record.
You can also update the SET statement to SELECT #Var = AllWos FROM Inserted
Hold on a second, if userid is your PK then Z_MobileSyncFilters will not have data yet, this is also an instead of trigger
this wholw block doesn't do anything really, why do you need this trigger?
DELETE FROM Z_MobileSyncFilters WHERE UserID = (SELECT UserID FROM INSERTED);
INSERT INTO Z_MobileSyncFilters
SELECT *
FROM INSERTED;
you second trigger is flawed because it will faile if you have a multi row operation
why do you have 2 insert trigger (1 instead 1 after) on this table?