trigger to only pass on real changes to the audit table - sql

I wish to only pass on changes made to ProjectID to the audit table . If for instance changes are made , but the valve stays the same , then a record is not added to the audit table .
CREATE TRIGGER trgAfterUpdate ON [dbo].[Assets]
FOR UPDATE
AS
declare #assetid int;
declare #assetname nvarchar(max);
declare #projectid int;
declare #audit_action varchar(100);
select #assetid=i.AssetID from inserted i;
select #assetname=i.AssetName from inserted i;
select #projectid=i.ProjectID from inserted i;
if update(ProjectID)
set #audit_action='Updated Record -- After Update Trigger.';
insert into Asset_Test_Audit(AssetID,AssetName,Projectid,Audit_Action,Audit_Timestamp)
values(#assetid,#assetname,#projectid,#audit_action,getdate());
PRINT 'AFTER UPDATE Trigger fired.'
GO

you have to compare the results from inserted table with the deleted table like below:
CREATE TRIGGER trgAfterUpdate ON [dbo].[Assets]
FOR UPDATE
AS
declare #assetid int;
declare #assetname nvarchar(max);
declare #projectid int;
declare #audit_action varchar(100);
select #assetid=i.AssetID from inserted i;
select #assetname=i.AssetName from inserted i;
select #projectid=i.ProjectID from inserted i;
if update(ProjectID) and exists (select * from deleted
where AssetID<>#assetid or AssetName<>#assetname or ProjectID<>#projectid)
begin
set #audit_action='Updated Record -- After Update Trigger.';
insert into Asset_Test_Audit(AssetID,AssetName,Projectid,Audit_Action,Audit_Timestamp)
values(#assetid,#assetname,#projectid,#audit_action,getdate());
PRINT 'AFTER UPDATE Trigger fired.'
end
GO

You might want to do it a bit differently. Inserted and deleted tables hold previous and current states of ALL records updated, and you need to audit them all:
CREATE TRIGGER trgAfterUpdate ON [dbo].[Assets]
AFTER UPDATE
AS
set NoCount ON -- So that your insert does not mess with update rowcount
if update(ProjectID)
begin
insert into Asset_Test_Audit(AssetID, AssetName, Projectid, Audit_Action, Audit_Timestamp)
select AssetID, AssetName, ProjectID, 'Updated Record -- After Update Trigger.', getdate()
from Inserted
where not exists (select null from Deleted where Deleted.AssetID = Inserted.AssetID and Deleted.ProjectID = Inserted.ProjectID)
end
PRINT 'AFTER UPDATE Trigger fired.'
GO
A few pointers: do data retrieval in one select; instead of three selects that retrieve columns from Inserted, use one:
select #assetid=i.AssetID, #assetname=i.AssetName, #projectid=i.ProjectID from inserted i;
update(ColumnName) does not imply that value of that column has changed, but merely states that it participated in update:
update assets set ProjectID = ProjectID
still returns true in if update(ProjectID).
Use begin..end even when not necessary as it will save you a headache down the road. Sadly you will not know about the save, but you will certainly know (and feel the pain) if you don't.

Related

Multiple rows are getting inserted into a table (which is not desired) as part of a stored procedure

Update: This still remain a mystery. Checked the calling code and we did not find anything that would make the SP run in a loop.
For now we have split the SP into two which seems to have arrested the issue although not able to reason how that has helped out.
Database: MS SQL Server.
I have a SP which performs few operations - i.e inserts a row into 3 tables based on certain status as part of that SP being called.
It is getting called from our web application based on a user action.
We have cases, few times a day where the same row gets inserted multiple times (sometime more than 50+) with the same values in each row except that if you look at the datetime when the row was inserted there is a difference of few milliseconds. So it is unlikely that the user is initiating that action.
This SP is not running in a Transaction or with any locks however it is getting called probably concurrently multiple times as we have many concurrent users on the web application invoking this action.
My question is what is causing the same row to insert so many times? If concurrent execution of SP was an issue where we are updating same row then it is understood one may overwrite the other. However in this case each user calls in the SP with different parameters.
I have put the said operation in a Transaction to monitor the behavior however was looking to find out what exactly causes these kind of multiple inserts with same value just a few milliseconds apart?
USE [ABC]
GO
/****** Object: StoredProcedure [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog]
(
--Insert using bulk
#stdfrm_id int,
#course_id int,
#stdfrm_scrt_apprvby int,
#stdfrm_scrt_apprvcomment varchar(max),
#sRemainingDocs varchar(max),
#DTProcessAdmittedDocUploadScrutiny AS dbo.MyDTProcessAdmittedDocUploadScrutiny READONLY
)
AS
BEGIN
DECLARE #result char
SET #result='N'
--New
declare #AuditCount int=0;
select #AuditCount=count(scrtaudit_id) from tbl_ProcessAdmittedScrutinyAuditLog
where stdfrm_id=#stdfrm_id and stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
and stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment and convert(date,stdfrm_scrt_apprvon,103)=convert(date,getdate(),103)
--Checked extra conditon to avoid repeatation
if(#AuditCount=0)
BEGIN
--Call Insert
BEGIN TRY
/*Remaining Documents----------*/
DECLARE #sdtdoc_id Table (n int primary key identity(1,1), id int)
if(#sRemainingDocs is not null)
begin
--INSERT INTO #sdtdoc_id (id) SELECT Name from splitstring(#sRemainingDocs)
INSERT INTO #sdtdoc_id (id) SELECT [Value] from dbo.FN_ListToTable(#sRemainingDocs,',')
end
Declare #isRemaining int=0;
SELECT #isRemaining=Count(*) FROM #sdtdoc_id
/*Calculate stdfrm_scrt_apprvstatus*/
Declare #stdfrm_scrt_apprvstatus char(1)='A';--Approved
Declare #TotalDescripancies int;
select #TotalDescripancies=count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y'
if(#isRemaining>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
else if exists (select count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y')
begin
if(#TotalDescripancies>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
end
/* Check if Discrepancies Found first time then assign to Checker o.w assign to direct college like grievance*/
if(#stdfrm_scrt_apprvstatus='H')
begin
declare #countAuditLog int=0;
select #countAuditLog=count(stdfrm_id) from tbl_ProcessAdmittedScrutinyAuditLog where stdfrm_id =#stdfrm_id
if (#countAuditLog=0)
begin
set #stdfrm_scrt_apprvstatus='G'--'E';--Discrepancies Found set Edit request assign to Checker
end
--else if (#countAuditLog=1)
-- begin
--set #stdfrm_scrt_apprvstatus='G';--Discrepancies Found set Grievance assign to college
-- end
end
/*----------------------*/
/*Update status in original table-----*/
Update tbl_ProcessAdmitted set stdfrm_scrt_apprvstatus=#stdfrm_scrt_apprvstatus
,stdfrm_scrt_apprvon=getdate(),stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
,stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment
where stdfrm_id =#stdfrm_id
/*Add in Main Student Log-----------*/
/********* The row here gets inserted multiple times *******************/
INSERT into tbl_ProcessAdmittedScrutinyAuditLog
(stdfrm_id, stdfrm_scrt_apprvstatus, stdfrm_scrt_apprvon, stdfrm_scrt_apprvby, stdfrm_scrt_apprvcomment )
values
(#stdfrm_id, #stdfrm_scrt_apprvstatus, getdate(), #stdfrm_scrt_apprvby, #stdfrm_scrt_apprvcomment)
DECLARE #scrtaudit_id int =##identity
/*Completed -------------------------*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutiny WHERE stdfrm_id =#stdfrm_id
SET NOCOUNT ON;
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutiny
(stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Scrutiny Document Log -------------------------*/
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutinyAuditLog
(scrtaudit_id,stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #scrtaudit_id,#stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Remaining Documents Insert into table*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutinyRemiaing WHERE stdfrm_id =#stdfrm_id
DECLARE #Id int,#doc_id int
WHILE (SELECT Count(*) FROM #sdtdoc_id) > 0
BEGIN
Select Top 1 #Id = n,#doc_id=id From #sdtdoc_id
--Do some processing here
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaing(stdfrm_id, doc_id )
values (#stdfrm_id,#doc_id)
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaingAuditLog
(scrtaudit_id, stdfrm_id, doc_id )
values (#scrtaudit_id,#stdfrm_id,#doc_id)
DELETE FROM #sdtdoc_id WHERE n = #Id
END --Begin end While
/*End Remaining Documents-----------*/
SET #result=#stdfrm_scrt_apprvstatus
END TRY
BEGIN CATCH
SET #result='N'
insert into tbl_ErrorSql( ErrorMessage, stdfrm_id)
values(coalesce(Error_Message(),ERROR_LINE()),#stdfrm_id)
END CATCH;
--End of Call Insert
END
SELECT #result
END

Trigger not calling for huge rows insert

I have one table which consists of one trigger which will be called if any insert or update operation performed on that table.
This trigger will insert a new row in other physical table.
First I am taking the entire data to be inserted into a temporary table and then I am inserting data into my physical table(which has trigger).
After performing insert operation all the records in the temporary table are getting inserted into physical table but the trigger is executing for only first record, for rest of the records it is not executing.
Can anyone please help me with this issue.
NOTE : With cursor it is working fine but for performance issue I don't want to use cursor.
ALTER TRIGGER [dbo].[MY_TRG]
ON [dbo].[T_EMP_DETAILS]
FOR INSERT , UPDATE
AS
BEGIN
IF UPDATE(S_EMPLOYEE_ID)OR UPDATE(S_GRADE_ID)OR UPDATE(D_EFFECTIVE_DATE) OR UPDATE(S_EMPLOYEE_STATUS)
BEGIN
DECLARE #EmpId varchar(6)
DECLARE #HeaderId Int
DECLARE #FYStartYear varchar(4)
DECLARE #EffDate Smalldatetime
DECLARE #UpdatedBy varchar(10)
DECLARE #ActionType varchar(1)
DECLARE #RowCount Int
DECLARE #EmpRowCount Int
DECLARE #AuditRowsCount Int
DECLARE #EMP_STATUS VARCHAR(1)
DECLARE #D_FIN_START_YEAR DATETIME
DECLARE #Food_Count int
SELECT #FYStartYear = CAST(YEAR(D_CURRENT_FY_ST_DATE)AS VARCHAR) FROM dbo.APPLICATION WHERE B_IS_CURRENT_FY = 1
SELECT #UpdatedBy = 'SHARDUL'
select #EmpId = S_EMPLOYEE_ID from inserted
select #HeaderId = N_HEADER_TXN_ID from inserted
select #EffDate = D_EFFECTIVE_DATE from inserted
select #FLEXI_AMT = N_FLEX_BASKET_AMT from inserted
select #EMP_STATUS = S_EMPLOYEE_STATUS from inserted
select #D_FIN_START_YEAR=D_FIN_START_DATE from inserted
SELECT #RowCount = count(*) from T_EMP_DETAILS
WHERE S_EMPLOYEE_ID = #EmpId and
SUBSTRING(CAST(D_EFFECTIVE_DATE AS VARCHAR),1,11) = SUBSTRING(CAST(#EffDate AS VARCHAR),1,11)
BEGIN
exec INSERT_DEFAULT_VALUES #EmpId,#HeaderId,#UpdatedBy
END
That's one of many reasons Bulk is so fast :). Read Bulk Insert syntax and you'll see FIRE_TRIGGERS parameter. Use it.
As I wrote in my comment - you are using inserted in improper way. As written now it will work only for 1 row.
The second one is a WEIRD number of variables, and only few are used, why?
Third - you are using SP in the end of batch, you need to post it's code, I bet there is some insert in it, maybe you could avoid using this SP and insert directly in some table from inserted.

after update trigger to deal with multiple rows

I have created trigger to log all updated rows either one row or multiple and log updated rows into another table. The trigger is working fine with one record but when I update entire table for example: update TBL_ADM_USER_GROUP set name = '123' only one record is logged into TBL_TestTable ? why only one row logged into TBL_TestTable and how to enable this trigger to log all updated rows ?
trigger:
CREATE TRIGGER [dbo].[Group_Update]
ON [dbo].[TBL_ADM_USER_GROUP]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #userid INT
DECLARE #name nvarchar(1000)
SELECT #userid = i.changer_user_id from inserted i;
select #name = i.name from inserted i;
INSERT INTO TBL_TestTable
VALUES (CONCAT('Group "' ,#name,'" is updated'), 'GROUP', 'Update', SYSDATETIME(), #userid)
END
In SQL Server trigger works per entire statement, not row by row. Use:
CREATE TRIGGER [dbo].[Group_Update]
ON [dbo].[TBL_ADM_USER_GROUP]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TBL_TestTable -- define column list for clarity
SELECT CONCAT('Group "' ,i.name,'" is updated'),
'GROUP', 'Update', SYSDATETIME(),
i.changer_user_id
FROM inserted i;
END

If statement in AFTER DELETE trigger not working

I have a trigger which I would like to do something if a certain condition is met.
If a user causes the system to remove a row from a security table, and that row happens to fit particular criteria, I want to run a stored proc. The problem that I'm running into is that the trigger behaves as though the if statement's criteria is not met. I know that the criteria is being met, because I tried piping the variable in question into a table, and the value I came up with was correct.
If I remove the IF statement, the procedure runs (though indiscriminately...it runs as expected, regardless of the value of the personorgroup field in the deleted table, which is not what I want it to do.)
Here is the trigger:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter TRIGGER dbo.SecurityPersonRemoval
ON dbo.[security]
AFTER delete
AS
BEGIN
SET NOCOUNT ON;
declare #person int
declare #thing int
select #person = personorgroup from deleted
select #thing = thing from deleted
if #person = '37671721'
exec docsadm.ethicalwall #thing
END
GO
Ideas?
Your statement probably fails because "deleted" can contain multiple records. Assigning the value of the select to a scalar will only return the first personID and the rest will be ignored. If you must call docsamd.ehticalwall for each person deleted, you could use a cursor:
declare #personId int,
#thingId int,
#fetchStatus int
declare myDeletedPeople cursor for select personorgroup, thing from deleted readonly
open myDeletedPeople
fetch myDeletedPeople into #personId, #thingId
select #fetchStatus = ##FETCH_STATUS
while (#fetchStatus=0)
begin
if(#personId = '37671721')
begin
exec docsadm.ethicalwall #thingId
end
fetch myDeletedPeople into #personId, #thingId
select #fetchStatus = ##FETCH_STATUS
end
close myDeletedPeople
deallocate myDeletedPeople

SQL Trigger on Insert, Delete, Updated

I have the following trigger
ALTER TRIGGER [dbo].[RefreshProject]
ON [dbo].[Project]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #percentage decimal(18,2)
DECLARE #ProjectID int
DECLARE #TASKID int
IF EXISTS(SELECT * FROM Inserted)
BEGIN
SELECT #ProjectID = Inserted.ProjectID, #TASKID = ISNULL(Inserted.TASKID,-1) from Inserted join Project on Inserted.ScheduleID = Project.ScheduleID
END
IF EXISTS(SELECT * FROM Deleted)
BEGIN
SELECT #ProjectID = Deleted.ProjectID,#TASKID = ISNULL(Deleted.TASKID,-1) from Deleted join Project on Deleted.ScheduleID = Project.ScheduleID
END
BEGIN
SET #percentage = (SELECT percentage from Project where ProjectID = #ProjectID)
EXEC LoadSummary #percentage,#ProjectID, #TASKID
END
END
For Inserts and updates I am able to get the ProjectID of the modified object, however when an item is deleted, I can not get the ProjectID or TaskID... Any Idea what I'm doing wrong?
The problem is that your trigger is an AFTER trigger, which means that the row has already been deleted from the Project table by the time the trigger is fired. So, you can't join the deleted view to Project, because the corresponding row won't exist. You will need to assign the variables you want directly from the deleted view.
Also, as Mitch's note mentions above, you may want to use a cursor to iterate over all rows in the inserted/deleted views if multiple rows can be updated at a time. Alternatively, you could raise an error at the beginning of your trigger if ##ROWCOUNT is greater than one, to prevent multiple row updates from causing your trigger to misbehave.