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.
Related
I created a trigger in SQL Server 2005 that inserts records into a history table whenever a deletion occurs in the source table. The records are getting inserted, but they are not getting deleted from the source table.
Here is my trigger:
CREATE TRIGGER TRG_EdiHistory
ON dbo.EDI10000
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
SET IDENTITY_INSERT dbo.EDI10500 ON;
INSERT INTO EDI10500(File_Id, Tp_Id, File_Name, File_Size, File_Data, Rec_Date, Content_Type, Update_Flag)
SELECT
File_Id, Tp_Id, File_Name, File_Size, File_Data,
Rec_Date, Content_Type, Update_Flag
FROM
DELETED
END
GO
I had to use an INSTEAD OF trigger because my tables contains Image type columns.
Please if anyone has any idea why this is happening.
Thank you.
* UPDATE *
CREATE TRIGGER TRG_EdiHistory
ON dbo.EDI10000
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
SET IDENTITY_INSERT dbo.EDI10500 ON;
INSERT INTO EDI10500 ([File_Id],Tp_Id,[File_Name],File_Size
,File_Data,Rec_Date,Content_Type,Update_Flag)
SELECT [File_Id], Tp_Id, [File_Name], File_Size, File_Data,
Rec_Date, Content_Type, Update_Flag
FROM DELETED
DELETE FROM dbo.EDI10000
WHERE EXISTS (SELECT 1
FROM DELETED
WHERE [FILE_ID] = dbo.EDI10000.[File_Id])
END
GO
Instead of Triggers , fire Instead of the triggering action. In your Case this instead of Trigger fires and Inserts data into your history table.
Note this trigger fires instead of the Delete command. So if you also want to Delete rows you will need to add Delete statement inside this trigger.
Having said this I think instead of Instead Trigger if you simply define an After Trigger with same definition as your this instead of trigger will makes things pretty simple. It will delete the rows from table and then insert rows into your history table as you are expecting it to work.
Update
Since you have mentioned you cannot use Image Data type inside an After Trigger in sql server 2005, I am not aware of this limitation. Well in this case you can simply add a delete statement inside your this existing instead of trigger.
DELETE FROM dbo.table
WHERE EXISTS (SELECT 1
FROM deleted
WHERE Pk_Column = table.PK_Column)
Instead of delete replaces the standard action of the DELETE statement.
http://technet.microsoft.com/en-us/library/ms191208(v=sql.105).aspx
Have a look here how to do what you really want:
How to create a before delete trigger in SQL Server?
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
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.
I have a SQL Server 2008 database. There are three terminals connected to it (A, B, C). There is a table SampleTable in the database, which reacts to any terminal activity. Every time there is some activity on any terminal, logged on to this DB, the new row is inserted into SampleTable.
I want to redirect traffic from one (C) of the three terminals to write to table RealTable and not SampleTable, but I have to do this on DB layer since services that write terminal activity to DB are in Black Box.
I already have some triggers working on SampleTable with the redirecting logic, but the problem is that rows are still being inserted into SampleTable.
What is the cleanest solution for this. I am certain that deleting rows in an inserting trigger is bad, bad, bad.
Please help.
Edit:
Our current logic is something like this (this is pseudo code):
ALTER TRIGGER DiffByTerminal
ON SampleTable
AFTER INSERT
AS
DECLARE #ActionCode VARCHAR(3),
#ActionTime DATETIME,
#TerminalId INT
SELECT #ActionCode = ins.ActionCode,
#ActionTime = ins.ActionTime,
#TerminalId = ins.TerminalId
FROM inserted ins
IF(#TerminalId = 'C')
BEGIN
INSERT INTO RealTable
(
...
)
VALUES
(
#ActionCode,
#ActionTime,
#TerminalId
)
END
In order to "intercept" something before a row gets inserted into a table, you need an INSTEAD OF trigger, not an AFTER trigger. So you can drop your existing trigger (which also included flawed logic that assumed all inserts would be single-row) and create this INSTEAD OF trigger instead:
DROP TRIGGER DiffByTerminal;
GO
CREATE TRIGGER dbo.DiffByTerminal
ON dbo.SampleTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.RealTable(...) SELECT ActionCode, ActionTime, TerminalID
FROM inserted
WHERE TerminalID = 'C';
INSERT dbo.SampleTable(...) SELECT ActionCode, ActionTime, TerminalID
FROM inserted
WHERE TerminalID <> 'C';
END
GO
This will handle single-row inserts and multi-row inserts consisting of (a) only C (b) only non-C and (c) a mix.
One of the easiest solution for you is INSTEAD OF trigger. Simply stating, it's trigger that "fires" on very action you decide and lets you "override" the default behavior of the action.
You can override the INSERT, DELETE and UPDATE statements for specific table/view (you use it a lot with views that combine data from different tables and you want make the view insert-able) using INSTEAD OF trigger, where you can put your logic. inside the trigger you can then call again to INSERT when it's appropriate, and you don't have to worry about recursion - INSTEAD OF triggers won't apply on statements from inside the trigger code itself.
Enjoy.
I'm using Sqlserver express and I can't do before updated trigger. There's a other way to do that?
MSSQL does not support BEFORE triggers. The closest you have is INSTEAD OF triggers but their behavior is different to that of BEFORE triggers in MySQL.
You can learn more about them here, and note that INSTEAD OF triggers "Specifies that the trigger is executed instead of the triggering SQL statement, thus overriding the actions of the triggering statements." Thus, actions on the update may not take place if the trigger is not properly written/handled. Cascading actions are also affected.
You may instead want to use a different approach to what you are trying to achieve.
It is true that there aren't "before triggers" in MSSQL. However, you could still track the changes that were made on the table, by using the "inserted" and "deleted" tables together. When an update causes the trigger to fire, the "inserted" table stores the new values and the "deleted" table stores the old values. Once having this info, you could relatively easy simulate the "before trigger" behaviour.
Can't be sure if this applied to SQL Server Express, but you can still access the "before" data even if your trigger is happening AFTER the update. You need to read the data from either the deleted or inserted table that is created on the fly when the table is changed. This is essentially what #Stamen says, but I still needed to explore further to understand that (helpful!) answer.
The deleted table stores copies of the affected rows during DELETE and
UPDATE statements. During the execution of a DELETE or UPDATE
statement, rows are deleted from the trigger table and transferred to
the deleted table...
The inserted table stores copies of the affected rows during INSERT
and UPDATE statements. During an insert or update transaction, new
rows are added to both the inserted table and the trigger table...
https://msdn.microsoft.com/en-us/library/ms191300.aspx
So you can create your trigger to read data from one of those tables, e.g.
CREATE TRIGGER <TriggerName> ON <TableName>
AFTER UPDATE
AS
BEGIN
INSERT INTO <HistoryTable> ( <columns...>, DateChanged )
SELECT <columns...>, getdate()
FROM deleted;
END;
My example is based on the one here:
http://www.seemoredata.com/en/showthread.php?134-Example-of-BEFORE-UPDATE-trigger-in-Sql-Server-good-for-Type-2-dimension-table-updates
sql-server triggers
T-SQL supports only AFTER and INSTEAD OF triggers, it does not feature a BEFORE trigger, as found in some other RDBMSs.
I believe you will want to use an INSTEAD OF trigger.
All "normal" triggers in SQL Server are "AFTER ..." triggers. There are no "BEFORE ..." triggers.
To do something before an update, check out INSTEAD OF UPDATE Triggers.
To do a BEFORE UPDATE in SQL Server I use a trick. I do a false update of the record (UPDATE Table SET Field = Field), in such way I get the previous image of the record.
Remember that when you use an instead trigger, it will not commit the insert unless you specifically tell it to in the trigger. Instead of really means do this instead of what you normally do, so none of the normal insert actions would happen.
Full example:
CREATE TRIGGER [dbo].[trig_020_Original_010_010_Gamechanger]
ON [dbo].[T_Original]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Old_Gamechanger int;
DECLARE #New_Gamechanger int;
-- Insert statements for trigger here
SELECT #Old_Gamechanger = Gamechanger from DELETED;
SELECT #New_Gamechanger = Gamechanger from INSERTED;
IF #Old_Gamechanger != #New_Gamechanger
BEGIN
INSERT INTO [dbo].T_History(ChangeDate, Reason, Callcenter_ID, Old_Gamechanger, New_Gamechanger)
SELECT GETDATE(), 'Time for a change', Callcenter_ID, #Old_Gamechanger, #New_Gamechanger
FROM deleted
;
END
END
The updated or deleted values are stored in DELETED. we can get it by the below method in trigger
Full example,
CREATE TRIGGER PRODUCT_UPDATE ON PRODUCTS
FOR UPDATE
AS
BEGIN
DECLARE #PRODUCT_NAME_OLD VARCHAR(100)
DECLARE #PRODUCT_NAME_NEW VARCHAR(100)
SELECT #PRODUCT_NAME_OLD = product_name from DELETED
SELECT #PRODUCT_NAME_NEW = product_name from INSERTED
END