after update trigger to deal with multiple rows - sql

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

Related

MS SQL trigger not inserting records for bulk insert

One of the external application is inserting more than 40K records in the SQL Azure table.
I have a trigger to process all of the required rows which matches the column value for the unique column value for the distinct record
Whenever the 40K+ record in inserted the trigger is not able to fetch or trigger for all of the records and just getting 1 and 2 records sometimes.
In trigger how can I get distinct column value and order by.
inserting into temptables insert fewer columns only and random
How can i do batch processing from the trigger for the bulk insert
/****** Object: Trigger [dbo].[PriceStagingInsertTrigger] Script Date:
29/09/2020 13:46:24 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[PriceStagingInsertTrigger] on [dbo].
[SalesPriceStaging]
AFTER INSERT
AS DECLARE #ItemNumber NVARCHAR(20),
#applicablefromdate datetime,
#partycodetype int
SELECT #ItemNumber = ins.ITEMNUMBER FROM INSERTED ins;
SELECT #applicablefromdate = ins.PRICEAPPLICABLEFROMDATE FROM INSERTED ins;
SELECT #partycodetype = ins.PARTYCODETYPE FROM INSERTED ins;
SELECT * into #temptables from inserted
EXEC dbo.spSalesPriceStaging #ItemNumber,#applicablefromdate,#partycodetype
PRINT 'Stored procedure spSalesPriceStaging executed and completed'
The (BAD) solution is :
ALTER TRIGGER [dbo].[PriceStagingInsertTrigger]
ON [dbo].[SalesPriceStaging]
AFTER INSERT
AS
SET NOCOUNT ON;
DECLARE #ItemNumber NVARCHAR(20),
#applicablefromdate datetime,
#partycodetype int;
DECLARE C CURSOR
FOR
SELECT ITEMNUMBER, PRICEAPPLICABLEFROMDATE, PARTYCODETYPE
FROM inserted;
OPEN C;
FETCH C INTO #ItemNumber, #applicablefromdate, #partycodetype;
WHILE ##fetch_status = 0
BEGIN
EXEC dbo.spSalesPriceStaging #ItemNumber,#applicablefromdate,#partycodetype;
FETCH C INTO #ItemNumber, #applicablefromdate, #partycodetype;
END;
CLOSE C;
DEALLOCATE C;
GO
As they say, trigger in SQL Server have a set based logic and fires only one time even if there is 3657435435143213213 rows that are inserted.
The presence of a variable in the code is generally a pattern a bad code design....

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

how to maintain table log (every changes) history in MS SQL by write trigger

We just want to maintain table log (every changes) history in MS SQL by write trigger please suggest
i tried but not working
CREATE TRIGGER [dbo].[update_ServiceDescriptionTable]
ON ServiceDescriptionMaster
After UPDATE
AS
BEGIN
declare #Rate money;
Select #Rate = Rate from inserted;
update [dbo].[ServiceDescriptionMasterlog] set Rate = #Rate
where Service_Description = '';
END
Ya good.
If you want to maintain evry changes log then you can insert in same log table with all field like as follows:
1) create same table like "ServiceDescriptionMasterlog" with one Extra Field (Column) Entry_DateTime set default bind getdate() method.
2) write a trigger on "ServiceDescriptionMaster" table as follows:
ALTER TRIGGER [dbo].[ServiceDescriptionMaster_OnUpdate]
ON [dbo].[ServiceDescriptionMaster]
After UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[ServiceDescriptionMasterLog]
(S_No,Rate,.....)
select S_No,Rate,.....
from Deleted;
END
you can also maintain on delete:
ALTER TRIGGER [dbo].[ServiceDescriptionMaster_OnDelete]
ON [dbo].[ServiceDescriptionMaster]
For Delete
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[ServiceDescriptionMasterLog]
(S_No,Rate,.....)
select S_No,Rate,.....
from Deleted;
END

trigger to only pass on real changes to the audit table

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.

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.