SQL Server update trigger executing twice - sql

I have the following trigger -
USE [DatabaseA]
GO
/****** Object: Trigger [dbo].[T_TableA_U] Script Date: 02/17/2014 18:08:44 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[T_TableA_U]
on [dbo].[TableA]
after update
as
set nocount on
DECLARE #HistoryType char(1) --"I"=insert, "U"=update, "D"=delete
DECLARE #RevisionID INT
SET #HistoryType = 'U'
SET #RevisionID = 0
INSERT INTO [DatabaseB].[dbo].[TableA]
(column1_revtm, column2_revtype,
column3_id, column4_revid, column5_type, ....)
SELECT
GETDATE(), #HistoryType,
a.column1_id, #RevisionID, a.column2, ....
FROM TableA a
inner join inserted i on a.column1_id = i.column1_id
If I manually update a row in the SOURCE it creates 1 new row in the DESTINATION. This is good.
When I use the 3rd party application I'm building this trigger on however, it's generating duplicate rows in the DESTINATION. All data is exactly the same except for the GETDATE() which tells me it's somehow duplicating the result which the application is forcing.
So how do I get around this? Is there a way to force a DISTINCT before the INSERT happens either in this trigger or could I create another trigger on the DESTINATION table that says if the row is a duplicate then only INSERT 1 of them?

I found the problem to be in the way the legacy application is designed. The table I had this trigger on had a PK/FK matching to another table. I reversed the trigger to run off the table with the FK and it works on a 1 to 1 basis with no duplicates so it would seem the legacy app updates both tables even if only 1 table is updated.

Related

Creating a trigger on [msdb].[dbo].restorehistory

I have a requirement to update or change the values of some fields in specific tables when someone restores a Prod database to a Test environment. I just want to change for example 'PROD' characters to 'Test' characters in certain fields once a database have been restored from Prod to Test environment. I have just created a small DB with two tables only and a trigger on [msdb].[dbo].restorehistory table, backed up the sample DB to simulate the scenario. But whenever I try restore there is nothing happening on the trigger.
From the below this is what I tried but it seems not working. It looks like the trigger is not hit when there is an [INSERT] event to the table[restorehistory].
USE [msdb]
GO
/****** Object: Trigger [dbo].[TriggerA] Script Date: 12/24/2022 8:22:59 PM ******/
CREATE TRIGGER [dbo].[TriggerA]
ON [msdb].[dbo].[restorehistory]
FOR INSERT
AS
DECLARE #DatabaseName AS VARCHAR(100)
SELECT #DatabaseName = DESTINATION_DATABASE_NAME
FROM INSERTED
IF #DatabaseName='TestDB'
BEGIN
UPDATE [TestDB].[dbo].<TABLE_X>
SET FIELD_A = REPLACE(FIELD_A,'_character_to_replace_','_replace_with_this_character_'),
FIELD_B = REPLACE(FIELD_B,'_character_to_replace_','_replace_with_this_character_')
WHERE
FIELD_A LIKE '%_character_to_replace_%'
END
Please advise if there is something I'm doing wrong or if there is any better alternative.

Trigger to update table column after insert?

I need to update a column in table after any record is added in same table
Here is my sql code
CREATE TRIGGER [dbo].[EmployeeInsert]
ON [dbo].[APP_Employees]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #EmployeeID AS bigint
SELECT #EmployeeID = ID FROM inserted
UPDATE [dbo].[APP_Employees]
SET [EmployeeTotalNumberOfAnnualLeave] = [EmployeeBalanceTheInitialNumberOfDaysOfAnnualLeaveIn]
WHERE ID=#EmployeeID
END
GO
and showing error
Msg 2714, Level 16, State 2, Procedure EmployeeInsert, Line 17
There is already an object named 'EmployeeInsert' in the database.
The error you're getting is because you have that trigger already, in your database. So if you want to create it again, you need to first drop the existing trigger (or use ALTER TRIGGER instead of CREATE TRIGGER to modify the existing trigger).
BUT: your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Inserted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
SELECT #EmployeeID = ID FROM inserted
It's undefined - you might get the values from arbitrary rows in Inserted.
You need to rewrite your entire trigger with the knowledge the Inserted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Inserted !
-- drop the existing trigger
DROP TRIGGER [dbo].[EmployeeInsert]
GO
-- create a new trigger
CREATE TRIGGER [dbo].[EmployeeInsert]
ON [dbo].[APP_Employees]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- update your table, using a set-based approach
-- from the "Inserted" pseudo table which CAN and WILL
-- contain multiple rows!
UPDATE [dbo].[APP_Employees]
SET [EmployeeTotalNumberOfAnnualLeave] = i.[EmployeeBalanceTheInitialNumberOfDaysOfAnnualLeaveIn]
FROM Inserted i
WHERE [dbo].[APP_Employees].ID = i.ID
END
GO

SQL Server 2012 trigger: Auditing. How to see previous value of a row without shadow table

I am trying to create an auditing table. I have a table called person.address in the AdventureWorks 2012 database.
I am using a trigger to capture changes to the table, the only problem is I do not know if it is possible to use a trigger to capture a row BEFORE it is edited. I am trying to save resources and overheads so trying to not use a shadow table. I know there is no "Before Insert" trigger. But is there any way to capture the information contained in a row, and when someone does an insert or update, this row can be written to my audit.table before the insert is completed?
Thank you.
Given a simplistic table with two rows:
CREATE TABLE dbo.foo(a INT PRIMARY KEY);
INSERT dbo.foo(a) VALUES(1),(2);
Then an update trigger simply to demonstrate:
CREATE TRIGGER dbo.trfoo ON dbo.foo FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
SELECT a FROM inserted;
SELECT a FROM deleted;
END
GO
The result of an action, such as:
UPDATE dbo.foo SET a += 1;
Results in:
a -- this is the *new* version of these rows
----
3
2
a -- this is the *old* version of these rows
----
2
1
Also, there is an INSTEAD OF INSERT trigger, which allows you to perform actions before the insert (they're not called BEFORE triggers because you still have to perform the insert yourself). More info here.

Basic SQL update trigger

I want to create a trigger that runs just before a row in a table is updated, and writes all the fields in the row before it is updated to an archive table. What would be the correct syntax required to gain access to the row fields before the update so that I can write them into my archive table?
EDIT :
So this should do what I want, but it doesn't seem to work. I get the error 'there is already an object called config_SystemSettings in the database :
CREATE TRIGGER [config].[UpdateSystemSettings]
ON [config].[SystemSetting]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
SELECT old.settingId, old.campId, old.settingKey, old.settingValue
into [history].[config_SystemSettings]
FROM [config].[SystemSetting] AS old
INNER JOIN deleted AS del ON del.settingId = old.settingId
END
GO
SELECT ... INTO always wants to create a new table - so use INSERT ... SELECT instead:
CREATE TRIGGER [config].[UpdateSystemSettings]
ON [config].[SystemSetting] AFTER UPDATE AS BEGIN
SET NOCOUNT ON;
insert into [history].[config_SystemSettings] (settingId,campId,settingKey,settingValue)
SELECT old.settingId, old.campId, old.settingKey, old.settingValue
FROM [config].[SystemSetting] AS old
INNER JOIN deleted AS del ON del.settingId = old.settingId
But you will have to explicitly create [history].[config_SystemSettings] first.

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.