How can I do a BEFORE UPDATED trigger with sql server? - sql

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

Related

Maximum Nesting of levels in SQL Server after trigger

I created a new trigger that would let me create a log after updating, however even though i think it should work i get the following error:
The maximum nesting level of views, stored procedures, functions or triggers was exceeded (limit: 32).
Is it possible that it has something to do with how the trigger is written?
ALTER TRIGGER [TR_Log_Notes]
ON ALUMNS
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Note_Text VARCHAR (30)
SET #Note_Text = 'Fields were updated'
INSERT INTO Log_Notes(Alumn_ID, Note_Text)
SELECT i.Alumn_ID, #NoteText
FROM INSERTED i
END
You are inserting into log_notes after updating a row in the same table. Is that really what you intend?
If you do intend the same table, then you should use a for trigger, and update each row before it goes into the table. However, I suspect that you want to create the trigger on a different table.

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.

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.

Recursive Update trigger issue in SQL 2005

Below is the code snippet with comments which describes the problem statement. We have an update trigger which internally calls another update trigger on the same table inspite of Recursive Trigger Enabled Property Set to false.
Would like to understand the reason for this as this is causing a havoc in my applications.
/* Drop statements for the table and triggers*/
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo]. [t_upd_TestTrigger_002]'))
DROP TRIGGER [dbo].[t_upd_TestTrigger_002]
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[t_upd_TestTrigger_002]'))
DROP TRIGGER [dbo].[t_upd_TestTrigger_001]
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestTrigger]') AND type in (N'U'))
DROP TABLE [dbo].[TestTrigger]
CREATE TABLE [dbo].[TestTrigger] /*Creating a test table*/
(
[InternalKey] INT NOT NULL,
[UserModified] varchar(50) DEFAULT SUSER_SNAME()
)
/* Please run the snippet below as seperate batch, else you will get
an error that 'CREATE TRIGGER' must be the first statement in a
query batch.
CREATING A UPDATE TRIGGER FOR THE TEST TABLE
*/
CREATE TRIGGER [t_upd_TestTrigger_001] ON [dbo].[TestTrigger]
FOR UPDATE
AS
BEGIN
--This trigger has some business logic which gets executed
print 'In Trigger 001 '
END
/* Please run the snippet below as separate batch, else you will
get an error that 'CREATE TRIGGER' must be the first statement
in a query batch.
CREATING Another UPDATE TRIGGER FOR THE TEST TABLE
This trigger updates the audit fields in the table and it has to be
a separate trigger, We cannot combine this with other update triggers -
So table TestTrigger will have two FOR UPDATE triggers
*/
CREATE TRIGGER [t_upd_TestTrigger_002] ON [dbo].[TestTrigger]
FOR UPDATE
AS
print 'bad guy starts'
UPDATE SRC
SET UserModified = SUSER_SNAME()
FROM inserted AS INS
INNER JOIN dbo.[TestTrigger] AS SRC
ON INS.InternalKey = SRC.InternalKey
print 'bad guy ends'
/* INSERTING TEST VALUE IN THE TEST TRIGGER TABLE*/
INSERT INTO dbo.[TestTrigger](InternalKey,UserModified)
SELECT 1 ,'Tester1' UNION ALL
SELECT 2,'Tester2' UNION ALL
SELECT 3 ,'Tester3'
/* TestTrigger table has 3 records, we will update the InternalKey
of first record from 1 to 4. We would expect following actions
1) [t_upd_TestTrigger_001] to be executed once
2) [t_upd_TestTrigger_002] to be executed once
3) A message that (1 row(s) affected) only once.
On Execution, i find that [t_upd_TestTrigger_002] internally triggers
[t_upd_TestTrigger_001].
Please note Database level property Recursive Triggers enabled is
set to false.
*/
/*UPDATE THE TABLE SEE THE MESSAGE IN RESULT WINDOW*/
UPDATE dbo.[TestTrigger]
SET InternalKey = 4
WHERE InternalKey = 1
"Recursive Triggers enabled" does not affect transitive triggers.
Which means that if trigger A updates a table in a manner that activates trigger B, and trigger B updates the same table, so that trigger A is run again, SQL Server has no way of detecting and inhibiting this endless loop. Especially since trigger B can update other tables, and a trigger on them could update the original table again - this could become as complex as you like.
Eventually, the trigger nesting level limit will be reached, and the loop stops.
I suspect that both of your triggers update the source table in some way. SQL Server can only detect recursive triggers if a trigger is activating itself. I suppose that's not the case for you. Restructuring the triggers is the only clean way out.
As a (hackery) idea: You could append a field to the table (data-type and value is irrelevant) that is updated by no operation but by triggers. Then change your second-order triggers to update that field. Add an IF UPDATE() check for that field to your first-order trigger. Prevent the now redundant update if the field has been set. If that makes sense. ;-)
MSDN: Using Nested Triggers, see sections "Direct recursion" and "Indirect recursion".
You can use IF UPDATE(), as Tomalak described, to skip trigger logic if UserModified is being updated.
Another possibility is to move the UserModified column to a separate table to avoid recursion.
If you want to stop this kind of behaviour across the database totally, "To disable indirect recursion, set the nested triggers server option to 0 using sp_configure. For more information, see Using Nested Triggers."
Of course, there's always the consideration that you may want to actually use nested triggers.

How do I prevent a database trigger from recursing?

I've got the following trigger on a table for a SQL Server 2008 database. It's recursing, so I need to stop it.
After I insert or update a record, I'm trying to simply update a single field on that table.
Here's the trigger :
ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
ON [dbo].[tblMedia]
BEFORE INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #IdMedia INTEGER,
#NewSubject NVARCHAR(200)
SELECT #IdMedia = IdMedia, #NewSubject = Title
FROM INSERTED
-- Now update the unique subject field.
-- NOTE: dbo.CreateUniqueSubject is my own function.
-- It just does some string manipulation.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(#NewSubject) +
CAST((IdMedia) AS VARCHAR(10))
WHERE tblMedia.IdMedia = #IdMedia
END
Can anyone tell me how I can prevent the trigger's insert from kicking off another trigger again?
Not sure if it is pertinent to the OP's question anymore, but in case you came here to find out how to prevent recursion or mutual recursion from happening in a trigger, you can test for this like so:
IF TRIGGER_NESTLEVEL() <= 1/*this update is not coming from some other trigger*/
MSDN link
I see three possibilities:
Disable trigger recursion:
This will prevent a trigger fired to call another trigger or calling itself again. To do this, execute this command:
ALTER DATABASE MyDataBase SET RECURSIVE_TRIGGERS OFF
GO
Use a trigger INSTEAD OF UPDATE, INSERT
Using a INSTEAD OF trigger you can control any column being updated/inserted, and even replacing before calling the command.
Control the trigger by preventing using IF UPDATE
Testing the column will tell you with a reasonable accuracy if you trigger is calling itself. To do this use the IF UPDATE() clause like:
ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
ON [dbo].[tblMedia]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #IdMedia INTEGER,
#NewSubject NVARCHAR(200)
IF UPDATE(UniqueTitle)
RETURN;
-- What is the new subject being inserted?
SELECT #IdMedia = IdMedia, #NewSubject = Title
FROM INSERTED
-- Now update the unique subject field.
-- NOTE: dbo.CreateUniqueSubject is my own function.
-- It just does some string manipulation.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(#NewSubject) +
CAST((IdMedia) AS VARCHAR(10))
WHERE tblMedia.IdMedia = #IdMedia
END
TRIGGER_NESTLEVEL can be used to prevent recursion of a specific trigger, but it is important to pass the object id of the trigger into the function. Otherwise you will also prevent the trigger from firing when an insert or update is made by another trigger:
IF TRIGGER_NESTLEVEL(OBJECT_ID('dbo.mytrigger')) > 1
BEGIN
PRINT 'mytrigger exiting because TRIGGER_NESTLEVEL > 1 ';
RETURN;
END;
From MSDN:
When no parameters are specified, TRIGGER_NESTLEVEL returns the total
number of triggers on the call stack. This includes itself.
Reference:
Avoiding recursive triggers
ALTER DATABASE <dbname> SET RECURSIVE_TRIGGERS OFF
RECURSIVE_TRIGGERS { ON | OFF }
ON Recursive firing of AFTER triggers is allowed.
OFF Only direct recursive firing of AFTER triggers is not allowed. To
also disable indirect recursion of
AFTER triggers, set the nested
triggers server option to 0 by using
sp_configure.
Only direct recursion is prevented when RECURSIVE_TRIGGERS is set to OFF.
To disable indirect recursion, you
must also set the nested triggers
server option to 0.
The status of this option can be determined by examining the
is_recursive_triggers_on column in the
sys.databases catalog view or the
IsRecursiveTriggersEnabled property of
the DATABASEPROPERTYEX function.
I think i got it :)
When the title is getting 'updated' (read: inserted or updated), then update the unique subject. When the trigger gets ran a second time, the uniquesubject field is getting updated, so it stop and leaves the trigger.
Also, i've made it handle MULTIPLE rows that get changed -> I always forget about this with triggers.
ALTER TRIGGER [dbo].[tblMediaAfterInsert]
ON [dbo].[tblMedia]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
-- If the Title is getting inserted OR updated then update the unique subject.
IF UPDATE(Title) BEGIN
-- Now update all the unique subject fields that have been inserted or updated.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(b.Title) +
CAST((b.IdMedia) AS VARCHAR(10))
FROM tblMedia a
INNER JOIN INSERTED b on a.IdMedia = b.IdMedia
END
END
You can have a separate NULLABLE column indicating whether the UniqueTitle was set.
Set it to true value in a trigger, and have the trigger do nothing if it's value is true in "INSERTED"
For completeness sake, I will add a few things. If you have a particular after trigger that you only want to run once, you can set it up to run last using sp_settriggerorder.
I would also consider if it might not be best to combine the triggers that are doing the recursion into one trigger.