AUDIT TRAIL OF TABLES - sql

I want to do audit trail on specific table like
what inserted,updated,deleted in table and all this logs are save in one table
I am using sql server 2012 .
Can any one please help me with how to achieve this?
Please note - Use of cursor is restricted

create an after trigger on that table and insert the records into the log table .
create trigger <trigger_name> after insert/update/delete/ on
table <orig table>
begin
insert into the log tables ('all the fields that you require');
end

Try using CDC (Change Data Capture). A very helpful tool which will help to manage the Audit Trail
Read the article from MSDN

This can be achieve using Triggers. Trigger will make your DML operations slower, if large Insert, Delete and Update operations are happening on your table. If it's small table you can create TRIGGER like below, to log the rows to another table based on the action occurred.
You can make use of Inserted and Deleted magic tables which hold the rows which are being Inserted and Deleted inside a trigger.
There is another alternate if you need more control over auditing using CDC (Change Data Capture).
CREATE TABLE TrailTable
(
Id INT,
Name VARCHAR(100)
);
CREATE TABLE TrailTableLog
(
Id INT,
Name VARCHAR(100),
Action CHAR(3)
);
Insert Into TrailTable VALUES (1,'Vi');
Insert Into TrailTable VALUES (2,'Vr');
Insert Into TrailTable VALUES (3,'An');
Insert Into TrailTable VALUES (4,'Ma');
CREATE TRIGGER dbo.TRG_IDU_TrailTable
ON dbo.TrailTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Action as char(1);
SET #Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
IF(#Action = 'I')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'I' FROM INSERTED;
END
IF(#Action = 'D')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'D' FROM DELETED;
END
IF(#Action = 'U')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-D' FROM INSERTED; -- Records Deleted to Update
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-I' FROM INSERTED; --Records Inserted to Update
END
END

Related

How to get a inserted id in other table from inside a trigger?

I have 3 tables tbl_Users, tbl_Protocol and tbl_ProtocolDetails and inside of my trigger on Users, I have to inserted into Protocol and then insert into ProtocolDetails, but I don't know how work the inserted scope.
Something like that:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #UserId = Int
DECLARE #ProtocolId = Int
DECLARE #UserDetail = NVARCHAR(255)
SELECT
#UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
FROM INSERTED
INSERT INTO tbl_Protocol (user_id, inserted_date)
VALUES (#UserId, GetDate())
-- Return Inserted Id from tbl_Protocol into #ProtocolDetail then
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
VALUES (#ProtocolId, #UserDetail)
END
Your trigger has a MAJOR flaw in that you seems to expect to always have just a single row in the Inserted table - that is not the case, since the trigger will be called once per statement (not once for each row), so if you insert 20 rows at once, the trigger is called only once, and the Inserted pseudo table contains 20 rows.
Therefore, code like this:
Select #UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
From INSERTED;
will fail, since you'll retrieve only one (arbitrary) row from the Inserted table, and you'll ignore all other rows that might be in Inserted.
You need to take that into account when programming your trigger! You have to do this in a proper, set-based fashion - not row-by-agonizing-row stlye!
Try this code:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
-- declare an internal table variable to hold the inserted "ProtocolId" values
DECLARE #IdTable TABLE (UserId INT, ProtocolId INT);
-- insert into the "tbl_Protocol" table from the "Inserted" pseudo table
-- keep track of the inserted new ID values in the #IdTable
INSERT INTO tbl_Protocol (user_id, inserted_date)
OUTPUT Inserted.user_id, Inserted.ProtocolId INTO #IdTable(UserId, ProtocolId)
SELECT user_id, SYSDATETIME()
FROM Inserted;
-- insert into the "tbl_ProtocolDetails" table from both the #IdTable,
-- as well as the "Inserted" pseudo table, to get all the necessary values
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
SELECT
t.ProtocolId,
i.user_detail + '#' + i.user_explanation
FROM
#IdTable t
INNER JOIN
Inserted i ON i.user_id = t.UserId
END
There is nothing in this trigger that would handle a multiple insert/update statement. You will need to either use one scenario that will handle multiple records or check how many records were effected with a IF ##ROWCOUNT = 1 else statement. In your example, I would just use something like
insert into tbl_Protocol(user_id, inserted_date)
select user_id, user_detail + '#' + user_explanation
From INSERTED;
As for your detail table, I see Marc corrected his answer to include the multiple lines and has a simple solution or you can create a second trigger on the tbl_Protocol. Another solution I have used in the past is a temp table for processing when I have very complicated triggers.

SQL trigger trouble with TRY--CATCH block for INSERT command

Ok, so I want to create a trigger that fires when someone tries to insert into the table "products" and checks for a valid foreign key. For right now (This is NOT the end design in the least, but I'm using it for testing), I want the trigger to check that the inserted line references a valid ID in the Manufacturer table, and if no such row exists in the Manufacturer table, insert one with the proper ID and some general information for the remaining fields. My current code is as follows:
create trigger checkman
on dbo.products
instead of insert
as
declare
#manid char(5),
#manName varchar(50),
#transactionName varchar(20) = 'transaction1'
Begin
select #manid=Man_ID from Inserted
begin try
/*begin tran #transactionName*/
Insert into Manufacturers (Man_ID, Man_Name, Man_Description) VALUES #manid, 'Unknown Name', 'This is an unknown manufacturer');
insert into dbo.products select * from inserted;
end try
begin catch
/*rollback tran #transactionName;*/
insert into dbo.products select * from inserted;
end catch
End
The problem is that whenever I run my insert with a Manufacturer ID that already exists, I get this error:
(0 row(s) affected)
Msg 3930, Level 16, State 1, Procedure checkman, Line 20
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
The statement has been terminated.
I get the same error when I put the "Insert into Manufacturers" line in the catch block, only this time it appears when I try to insert with an ID that doesn't yet exist.
Don't handle this in a try/catch block, it isn't necessary. Also you need to allow for the fact that inserted can contain multiple rows. You can do this using a NOT EXISTS query:
CREATE TRIGGER CheckMan
ON dbo.products
INSTEAD OF INSERT
AS
INSERT Manufacturers (Man_ID, Man_Name, Man_Description)
SELECT DISTINCT Man_ID, 'Unknown Name', 'This is an unknown manufacturer'
FROM inserted i
WHERE NOT EXISTS
( SELECT 1
FROM Manufacturers m
WHERE m.Man_ID = i.Man_ID
);
INSERT dbo.Products
SELECT *
FROM inserted;
HOWEVER, I don't advocate this approach, I think it would be much better to just rely on the referencial integrity provided by the foreign key itself, and if the Man_ID does not exist let the insert fail, and if necessary ensure all Man_IDs exist before even attempting to insert.
I changed your trigger to following. In doing so, I made a few implicit assumptions. Assuming here that Products is a 3 column table (col1 & col2 being the other columns apart from Man_ID).
Hope this helps
alter
trigger checkman
on dbo.products
instead of insert
as
declare
#manid char(5),
#col1 varchar(50),
#transactionName varchar(20) = 'transaction1',
#col2 Varchar(500)
Begin
select #manid=Man_ID, #col1 = col1, #col2 = col2 from Inserted
IF (NOT EXISTS (SELECT Man_ID
FROM Manufacturers Where Man_ID = #manid
))
INSERT INTO ManuFacturers (Man_ID, Man_name, Man_Description) Values (#manid, 'Unknown Name', 'This is an unknown manufacturer')
INSERT INTO Products (Man_ID, col1, col2) values (#manid, #col1, #col2)
--begin try
-- /*begin tran #transactionName*/
-- Insert into Manufacturers (Man_ID, Man_Name, Man_Description) VALUES (#manid, 'Unknown Name', 'This is an unknown manufacturer')
-- insert into dbo.products select * from inserted;
--end try
--begin catch
-- /*rollback tran #transactionName;*/
-- insert into dbo.products select * from inserted;
--end catch
End

Unable to copy Some coloumns using Trigger

I have a two database tables,that database tables connect with 1:1 relationship.Using a trigger i need to copy some coloumns data to Log table.(When New Insert or Update Happens)
Error (1) Invalid column name 'ItemTbl_ItemId'.
(2) Invalid column name 'Price'
(3)Column name or number of supplied values does not match table definition.
First Table has
Table Name - ItemTbl
ItemId, ItemName, ItemPrice,Comments, Brand_BrandId
Second Table
Table Name - ItemLog
ItemLogId, ItemTbl_ItemId,ItemName,ItemPrice,ModifiedDate
My Trigger
CREATE TRIGGER [dbo].[ItemHistoryTrigger]
ON [dbo].[ItemTbl]
FOR INSERT,UPDATE
AS
BEGIN
IF EXISTS(SELECT ItemId,ItemName,ItemPrice FROM INSERTED)
BEGIN
INSERT INTO [dbo].[ItemLog]
SELECT ItemTbl_ItemId,ItemName,ItemPrice FROM INSERTED;
END
END
I Just wanted to copy ItemName & ItemPrice from the first table to second table using trigger.
You have no column named ItemTbl_ItemId in the table you are creating the trigger (ItemTbl). Try this:
CREATE TRIGGER [dbo].[ItemHistoryTrigger]
ON [dbo].[ItemTbl]
FOR INSERT,UPDATE
AS
BEGIN
IF EXISTS(SELECT ItemId,ItemName,ItemPrice FROM INSERTED)
BEGIN
INSERT INTO [dbo].[ItemLog](ItemTbl_ItemId,ItemName,ItemPrice)
SELECT ItemId,ItemName,ItemPrice FROM INSERTED;
END
END
And this assumes that the ItemLog's columns ItemLogId and ModifiedDate are auto_incremented and have a default value respectively.

Can a storeProcecdure insert fire Trigger?

SQL Server 2008
I have trigger defined on
TABLE_A for 'INSTEAD OF INSERT' and TABLE_B for 'INSTEAD OF INSERT'.
Both the triggers perform merge with the inserted table.
TABLE_A insert is done by user/code and is working well, trigger for insert is fired.
I have Stored procedure SP_1 inside TABLE_A TRIGGER.
SP_1 Inserts data from TABLE_A into TABLE_B based on some conditions.
But the problem is when the stored procedure (SP_1) is inserting data, the trigger on TABLE_B is not fired and the data is just inserted as it is.
So can stored procedure inserts fire triggers?
Pseudo-code
ALTER TRIGGER [dbo].[trgtable_AInsert] ON [dbo].[TABLE_A]
Instead of INSERT
AS
BEGIN
SET NOCOUNT ON;
IF exists(SELECT * FROM INSERTED)
BEGIN
MERGE
.......
...........
..............
end
EXEC SP_1 #employee_id
end
ALTER TRIGGER [dbo].[trgtableB_Insert] ON [dbo].[TABLE_B]
Instead of INSERT
AS
BEGIN
SET NOCOUNT ON;
IF exists(SELECT * FROM INSERTED)
BEGIN
MERGE
.......
...........
..............
end
end
ALTER PROCEDURE [dbo].[SP_1] #employeeid int
AS
BEGIN
BEGIN TRANSACTION
insert into TABLE_B
.......
...........
..............
from TABLE_A
where employee_ID is #employeeid
COMMIT TRANSACTION
END
Yes triggers can fired by stored procedure inserts!
But I think the problem is that you should try to use AFTER instead of INSTEAD OF triggers in this case. Becasue I can't see all of your code, but it is possible, that the insert is not done because you overrided it in the Instead Of triggers. With AFTER triggers you should have no problems with firing the second trigger.
This is too big for a comment, and needs formatting, so posting as an "answer".
Yes, triggers will fire in this case. Taking your example and slightly modifying it (note the warnings though):
create table Table_A (ID int not null)
go
create table Table_B (ID int not null)
GO
CREATE PROCEDURE [dbo].[SP_1] #employeeid int
AS
BEGIN
BEGIN TRANSACTION
insert into TABLE_B (ID)
SELECT ID from TABLE_A
where ID = #employeeid
COMMIT TRANSACTION
END
GO
Creating the triggers:
CREATE TRIGGER [dbo].[trgtable_AInsert] ON [dbo].[TABLE_A]
Instead of INSERT
AS
BEGIN
SET NOCOUNT ON;
IF exists(SELECT * FROM INSERTED)
BEGIN
MERGE
into Table_A a
using inserted i on a.id = i.id
when not matched then insert (ID) values (i.id);
end
--Wrong code, just for example
declare #employee_id int
select #employee_id = ID from inserted --BAD CODE, Ignores multiple rows
EXEC SP_1 #employee_id
end
GO
CREATE TRIGGER [dbo].[trgtableB_Insert] ON [dbo].[TABLE_B]
Instead of INSERT
AS
BEGIN
SET NOCOUNT ON;
IF exists(SELECT * FROM INSERTED)
BEGIN
MERGE
into Table_B b
using inserted i on b.id = i.id
when not matched then insert (ID) values (i.id+5);
end
end
GO
And executing a trial insert into Table_A:
insert into Table_A (ID) values (1),(2)
select * from Table_B
On my machine, at the present time, I get a final result of a single row with the value "7". Others may run this sample and get the result "6", because triggers only run once per statement, rather than once per row. But as you can see, both triggers have fired.
As i previously mentioned in the comments #András Ottó
Merge
using(... = "column with possible null values" AND
... = ... AND
... = ...
)
of merge was not working correctly and the records were always inserted.
1 = 1 and E=E and NULL=NULL is not true. (of-course sql 101)
I have overlooked this column and did not place where clause properly to get rid of null values so ended up inserting all the time. Fixing that everything ended up working.
Thanks for the help Every1. Cheers
Apologies.
I'm not going to mark it answered because it is purely my mistake which was not fully mentioned in the question.

How to create a trigger that uses INSERT , DELETE , UPDATE events

I want to create a trigger for logging.So i need event names of INSERT,UPDATE or DELETE.i.e : one of these statements is used in query execution my trigger will trig and starts logging.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER LogBuses
ON Bus_table
AFTER INSERT,DELETE
AS
BEGIN
DECLARE #PlateNo nvarchar(50)
IF INSERT//something like that-INSERTING- DELETING
SELECT #PlateNo=PlateNo from inserted
insert into Logger (EffectedTable,ActionName,EffectDate,EffectedID)
VALUES ('Bus_table','Insert',SYSDATETIME (),#PlateNo);
ELSE IF DELETE
SELECT #PlateNo=PlateNo from deleted
insert into Logger (EffectedTable,ActionName,EffectDate,EffectedID) VALUES ('Bus_table','Insert',SYSDATETIME (),#PlateNo);
END
GO
You use the inserted and deleted tables. It's inserted if just the inserted table is populated, deleted if just the deleted table is populated, and updated if both tables are populated. Use if exists (select 1 from inserted) to test.
if exists (select 1 from inserted) and exists (select 1 from deleted)
--update
else if exists (select 1 from inserted)
--insert
else if exists (select 1 from deleted)
--delete
Create a stored procedure that logs, and triggers for each event, that call the procedure, passing the needed data.