Trigger for insert, update, delete - sql-server

I want to insert rows into the audit table whenever an insert, update or delete takes place in the master table "Table1" - doesn't matter which column is changed/inserted. I also want to add I, U or D on insert, update or delete. For insert and delete I am checking if rows exist in the inserted and deleted table. What is the best way to approach update.
My code for insert and delete is :
CREATE TRIGGER [dbo].[tr_Table1_InsertUpdate_Table1History_Insert]
ON [dbo].[Table1]
FOR INSERT, DELETE, UPDATE
AS
BEGIN
IF EXISTS(SELECT * FROM Inserted)
BEGIN
INSERT INTO Table1History(...., ModificationType)
SELECT ..., 'I'
FROM Inserted
END
IF EXISTS(SELECT * FROM Deleted)
BEGIN
INSERT INTO Table1History(..., ModificationType)
SELECT ..., 'D'
FROM Deleted
END
END
GO
Kindly help!

For updates, the original values for the row will be added to the deleted table, and the new values for the row will be added to the inserted table. So, to identify inserts, deletes and updates you would do the following
Inserts - get the rows from inserted that are not in deleted
Deletes - get the rows from deleted that are not in inserted.
Updates - get the rows that are in both inserted and deleted

Below is an example of a trigger generated by ApexSQL Audit
It’s not a cheap tool but you can probably use it in trial mode to get the job done.
Notice the INSERT INTO dbo.AUDIT_LOG_DATA part and repeat it for every column you want to audit.
There are two tables in the background for storing the data and several stored procedures as well but this will get you going in the right direction.
CREATE TRIGGER [dbo].[tr_d_AUDIT_TableName]
ON [dbo].[TableName]
FOR DELETE
NOT FOR REPLICATION
AS
BEGIN
DECLARE
#IDENTITY_SAVE varchar(50),
#AUDIT_LOG_TRANSACTION_ID Int,
#PRIM_KEY nvarchar(4000),
--#TABLE_NAME nvarchar(4000),
#ROWS_COUNT int
SET NOCOUNT ON
Select #ROWS_COUNT=count(*) from deleted
Set #IDENTITY_SAVE = CAST(IsNull(##IDENTITY,1) AS varchar(50))
INSERT
INTO dbo.AUDIT_LOG_TRANSACTIONS
(
TABLE_NAME,
TABLE_SCHEMA,
AUDIT_ACTION_ID,
HOST_NAME,
APP_NAME,
MODIFIED_BY,
MODIFIED_DATE,
AFFECTED_ROWS,
[DATABASE]
)
values(
'TableName',
'dbo',
3, -- ACTION ID For DELETE
CASE
WHEN LEN(HOST_NAME()) < 1 THEN ' '
ELSE HOST_NAME()
END,
CASE
WHEN LEN(APP_NAME()) < 1 THEN ' '
ELSE APP_NAME()
END,
SUSER_SNAME(),
GETDATE(),
#ROWS_COUNT,
'DatabaseName'
)
Set #AUDIT_LOG_TRANSACTION_ID = SCOPE_IDENTITY()
INSERT
INTO dbo.AUDIT_LOG_DATA
(
AUDIT_LOG_TRANSACTION_ID,
PRIMARY_KEY_DATA,
COL_NAME,
OLD_VALUE_LONG,
DATA_TYPE
, KEY1
)
SELECT
#AUDIT_LOG_TRANSACTION_ID,
convert(nvarchar(1500), IsNull('[Order_ID]='+CONVERT(nvarchar(4000), OLD.[Order_ID], 0), '[Order_ID] Is Null')),
'Order_ID',
CONVERT(nvarchar(4000), OLD.[Order_ID], 0),
'A'
, CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[Order_ID], 0))
FROM deleted OLD
WHERE
OLD.[Order_ID] Is Not Null
END

Have you consider using AutoAudit?
AutoAudit is a SQL Server (2005, 2008)
Code-Gen utility that creates Audit
Trail Triggers with:
Created, CreatedBy, Modified, ModifiedBy, and RowVersion
(incrementing INT) columns to table
Insert event logged to Audit table
Updates old and new values logged to Audit table
Delete logs all final values to the Audit tbale
view to reconstruct deleted rows
UDF to reconstruct Row History
Schema Audit Trigger to track schema changes
Re-code-gens triggers when Alter Table changes the table

Related

Re-Inserting deleted rows into the same table SQL Server 2005

After searching many pages I still can't find the answer about re-inserting deleted rows in the same table - not another table.
I have a table named timetable with the primary key made up from 3 columns Schoolcode, Year, Term.
I need for some reason need to insert deleted rows into the same table.
I get the error
Violation of PRIMARY KEY constraint
with the following trigger
ALTER TRIGGER [dbo].[AFTER_delete_]
ON [dbo].timetable
AFTER delete
AS
BEGIN
IF EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO timetable
SELECT *
FROM deleted A
WHERE NOT EXISTS (SELECT 1 FROM timetable B
WHERE B.Schoolcode = A.Schoolcode
AND B.Year = A.Year
AND B.Term = A.Term);
END
END
thanks any way.I test the code below and that did work.
ALTER TRIGGER [dbo].[Instead_OfDelSert_Status]
ON [dbo].[Status]
INSTEAD OF delete,insert
AS
BEGIN
PRINT 'You must disable or delete Trigger Instead_OfDelSert_Status to insert or
delete rows!'
END

Issue with Trigger

I am working with an insert trigger and work fine. I am creating an insert trigger and take a backup in tblHist table.
I have two tables:
tblUser - creating this table for insert,update and delete purpose
tblHist - creating this table for store a record for history purpose
tblUser table design:
tblHist table design:
Then I create an insert and update trigger:
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERINSERT]
ON [dbo].[tblUser]
AFTER INSERT, UPDATE
--,DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
BEGIN
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress,
#countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM tblUser u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER INSERT update trigger fired.'
END
END
When I insert a record into the tblUser table, then it inserts a record into the tblHist table - this is working fine.
See below
Then I update a record then insert a history in tblHist table working fine.
but issue is when I add a code for delete a record functionality in trgr_tblUser_AFTERINSERT then delete functionality not work
And when I create a delete trigger separately then work fine
See below
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERDELETE]
ON [dbo].[tblUser]
FOR DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress, #countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM deleted u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER DELETE trigger fired.'
END
I want to add insert, update, delete trigger functionality In one trigger but not work.
What I am trying:
exist select 1 --- but not work
which place I am doing wrong need help
I highly doubt that your first trigger will work properly .... you're just selecting an arbitrary rows from your tblUser table - not even one that's necessarily just been inserted or updated ....
I would strongly recommend these changes:
creating a separate trigger for each operation - that makes the trigger simpler, since you don't need to first figure out what you're dealing with....
add a ModifiedOn DATETIME2(3) column to your tblHist to record the date & time stamp when the change occurred
also possibly add an Operation column to your tblHist - so that you can understand what operation (insert, update, delete) caused this entry in the history table
properly handle the Inserted and Deleted pseudo tables in your trigger code taking into account they can (and will!) contain multiple rows - handle them in a proper, set-based fashion
drop the PRINT - makes no sense inside a trigger....
Code would be something like:
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterInsert
ON dbo.tblUser
AFTER INSERT
AS
BEGIN
-- do an "INSERT INTO" ...
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
-- based on the "Inserted" pseudo table, and use proper set-based approach
SELECT
SYSDATETIME(),
i.userid, i.username, i.useraddress, i.countryname, i.statename, i.cityname
FROM
Inserted i;
END
END
and
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterDelete
ON dbo.tblUser
AFTER DELETE
AS
BEGIN
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
SELECT
SYSDATETIME(),
d.userid, d.username, d.useraddress, d.countryname, d.statename, d.cityname
FROM
Deleted d;
END
END
If you want to check whether it was an insert, update or delete, you have to examine the row count of the inserted and deleted pseudotables - for an insert, records are only present in inserted, for delete they are only present in deleted. An update has both so you can tell the old values (deleted) and the new (inserted)
Make your life easy; put twice as many columns in your hist (should be called UserHist, no?) table as your user table, and make them e.g. old_username, new_username.. an insert the result of a full outer join between the inserted and deleted tables, this way you can tell if it was an insert, update or delete and particularly for an insert, what changed to what
Alternatively, use something like
IF EXISTS(SELECT null FROM inserted)
IF EXISTS(SELECT null FROM deleted)
--it was an update
ELSE
--it was an insert
END;
ELSE
--it was a delete
END;
Or, make 3 separate triggers
Final point of note, you're doing these queries wrong - you're declaring a bunch of variables (that can only hold a single value)and selecting the values from inserted/deleted into them but those pseudotables can have more than one row, if the query affected multiple rows (such as DELETE FROM user WHERE name = 'John')
You should be doing your operations in a bunch-of-rows way, not a "single row" way:
INSERT INTO tblHist
SELECT * FROM inserted
This can insert multiple rows into hist, and this is the way you should always think about doing things in SQLServer.. Even if you only ever insert one row and your inserted pseudotable has one row, you must get into the habit of treating it as "a collection of rows with one entry" so that any code you write won't fall apart when one day it becomes "a collection of rows with multiple entries"
Note in one of your attempts you did not select from inserted, you selected from the users table - this is wrong:
Of course, an AFTER delete trigger will insert nothing if you just deleted the only row from tblusers, but you shouldn't be using tblusers anyway

How to create a roll back of a table that you updated in sql

I have an sql query that updates a table based on a condition.I am creating a migration file in visual studio, how do i go about adding a roll back to ensure that the changes i updated into a file goes back to how it was.
INSERT INTO Table(ID,Name,SiteID,Surname)
SELECT
(SELECT MAX(ID) FROM Table) + ROW_NUMBER()OVER (ORDER BY ID),
Name,
10100,
Surname,
FROM Table
WHERE SiteID = 10000 --so it will copy this data 10000 and make a new entry of 10100
Can you advise on how to create a rollback so that it will delete all the 10100 entries and go back to been how it was
can I just say ?
delete
from table
where siteID=10100
Is this efficient? for a roll back
A DELETE statement, is just that, a DELETE statement. Rolling back something means undoing what has so far been done in the uncommitted transaction. This may not be "deleting", it might be undoing an UPDATE, or returning a row that was previously deleted, or even DDL changes.
In your case, if you want to remove the row you inserted earlier, then a DELETE statement is what you're after. That's not rolling back though. Here's an example of a ROLLBACK (and COMMIT):
--BEGIN a Transaction
BEGIN TRANSACTION Creation;
--Create a table
CREATE TABLE #Sample (ID int IDENTITY(1,1), String varchar(10));
-- insert a row
INSERT INTO #Sample (String)
VALUES ('Hello');
--Rollback the transactions
ROLLBACK TRANSACTION Creation;
--Now, not only has the row never been inserted, the table was not created!
--This will error
SELECT *
FROM #Sample;
GO
--Now, let's create and COMMIT that table this time:
BEGIN TRANSACTION Creation2;
--Create a table
CREATE TABLE #Sample (ID int IDENTITY(1,1), String varchar(10));
-- insert a row
INSERT INTO #Sample (String)
VALUES ('Hello');
--And commit
COMMIT TRANSACTION Creation2;
GO
--Hazaar! data
SELECT *
FROM #Sample;
GO
--And finally, a little play around with some data
BEGIN TRANSACTION Data1;
INSERT INTO #Sample (String)
VALUES ('These'),('are'),('more'),('values');
--Let's Delete the Hello as well
DELETE
FROM #Sample
WHERE ID = 1;
--Inspect mid transction
SELECT *
FROM #Sample;
--Rollback!
ROLLBACK TRANSACTION Data1;
--Oh, the values have gone!
SELECT *
FROM #Sample;
--Notice, however, the ID still increments:
INSERT INTO #Sample (String)
VALUES ('Goodbye');
--Goodbye is ID 6
SELECT *
FROM #Sample;
GO
DROP TABLE #Sample;
Hope that helps explain what a ROLLBACK is for you in SQL Server terms.

Creating a trigger on SQL Server 2008 R2 to catch a record update

Using Great Plains here and one of our users keeps screwing up customer data so we want to put a trigger on the customer table and so we can find out who it is.
Anyway, I created a table called audit_RM00101 as follows:
DATE nchar(10)
CUSTNMBR char(15)
CUSTNAME char(65)
UPSZONE char(3)
SALSTERR char(15)
USERID nchar(100)
I want to capture those same fields from the table I want to audit so I wrote the trigger as follows:
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
FOR UPDATE
AS
DECLARE #UserID VARCHAR(128)
SELECT #UserID = system_user
INSERT INTO audit_RM00101
SELECT DATE, CUSTNMBR, CUSTNAME, UPSZONE, SALSTERR, #UserID FROM UPDATED
The trigger gets created just fine but when I try to test it by updating a customer record in Great Plains, Great Plains throws up an ugly error and the trigger doesn't get fired.
What am I doing wrong here?
Thanks.
in a trigger, you get the DELETED and INSERTED tables, there is no UPDATED, so replace FROM UPDATED with FROM INSERTED
also try to fix your USERID column, your audit_RM00101.USERID is a nchar(100) while #UserID is a VARCHAR(128).
EDIT based on OPs comment: Ah, so there is no way to audit when a table is updated by using a trigger?
in a trigger when deleting, DELETED is populated, but INSERTED is empty
in a trigger when updating, DELETED is populated with the original value, and INSERTED is populated with the newly updated values
in a trigger when inserting, DELETED is empty, but INSERTED has the newly inserted values
There is no UPDATED in SQL Server; just inserted and deleted.
Also, it makes sense to add IF ##ROWCOUNT = 0 RETURN in the very beginning of triger's body.
When UPDATE takes place, both inserted and deleted tables are not empty. You may add the following code to make sure you handle UPDATE, not insert/delete:
IF EXISTS(SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
-- handle update
END ;
It's not really important for your trigger because you specify just FOR UPDATE, it would be important if you had, for instance, FOR UPDATE, INSERT, DELETE.
we have only two magic tables called INSERTED and DELETED
update indirectly is a Delete statement followed by Insert statement. so you have to update the column's value which is present in INSERTED.
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
AFTER UPDATE
AS
BEGIN
DECLARE #INSERTED INT, #DELETED INT
SET #INSERTED = SELECT COUNT(*) FROM INSERTED
SET #DELETED = SELECT COUNT(*) FROM DELETED
IF #INSERTED = 1 AND #DELETED = 1
BEGIN
UPDATE TABLE1
SET COL1 = INSERTED_COL1
WHERE IDCOL = INSERTED_IDCOL
END
END

SQL Server 2005 How can I set up an audit table that records the column name updated?

given this table definition
create table herb.app (appId int identity primary key
, application varchar(15) unique
, customerName varchar(35),LoanProtectionInsurance bit
, State varchar(3),Address varchar(50),LoanAmt money
,addedBy varchar(7) not null,AddedDt smalldatetime default getdate())
I believe changes will be minimal, usually only a single field, and very sparse.
So I created this table:
create table herb.appAudit(appAuditId int primary key
, field varchar(20), oldValue varchar(50),ChangedBy varchar(7) not null,AddedDt smalldatetime default getdate())
How in a trigger can I get the column name of the value of what was changed to store it? I know how to get the value by joining the deleted table.
Use the inserted and deleted tables. Nigel Rivett wrote a great generic audit trail trigger using these tables. It is fairly complex SQL code, but it highlights some pretty cool ways of pulling together the information and once you understand them you can create a custom solution using his ideas as inspiration, or you could just use his script.
Here are the important ideas about the tables:
On an insert, inserted holds the inserted values and deleted is empty.
On an update, inserted holds the new values and deleted holds the old values.
On a delete, deleted holds the deleted values and inserted is empty.
The structure of the inserted and deleted tables (if not empty) are identical to the target table.
You can determine the column names from system tables and iterate on them as illustrated in Nigel's code.
if exists (select * from inserted)
if exists (select * from deleted)
-- this is an update
...
else
-- this is an insert
...
else
-- this is a delete
...
-- For updates to a specific field
SELECT d.[MyField] AS OldValue, i.[MyField] AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.[MyPrimaryKeyField] = d.[MyPrimaryKeyField]
-- For your table
SELECT d.CustomerName AS OldValue, i.CustomerName AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.appId = d.appId
If you really need this kind of auditing in a way that's critical to your business look at SQL Server 2008's Change Data Capture feature. That feature alone could justify the cost of an upgrade.
something like this for each field you want to track
if UPDATE(Track_ID)
begin
insert into [log].DataChanges
(
dcColumnName,
dcID,
dcDataBefore,
dcDataAfter,
dcDateChanged,
dcUser,
dcTableName
)
select
'Track_ID',
d.Data_ID,
coalesce(d.Track_ID,-666),
coalesce(i.Track_ID,-666),
getdate(),
#user,
#table
from inserted i
join deleted d on i.Data_ID=d.Data_ID
and coalesce(d.Track_ID,-666)<>coalesce(i.Track_ID,-666)
end
'Track_ID' is the name of the field, and d.Data_ID is the primary key of the table your tracking. #user is the user making the changes, and #table would be the table your keeping track of changes in case you're tracking more than one table in the same log table
Here's my quick and dirty audit table solution. (from http://freachable.net/2010/09/29/QuickAndDirtySQLAuditTable.aspx)
CREATE TABLE audit(
[on] datetime not null default getutcdate(),
[by] varchar(255) not null default system_user+','+AppName(),
was xml null,
[is] xml null
)
CREATE TRIGGER mytable_audit ON mytable for insert, update, delete as
INSERT audit(was,[is]) values(
(select * from deleted as [mytable] for xml auto,type),
(select * from inserted as [mytable] for xml auto,type)
)

Resources