SQL Trigger on Insert, Delete, Updated - sql

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.

Related

'INSERT' stored procedure with validation

I'm trying to create a stored procedure where I'm inserting a new office into the OFFICE table I have in my database.
I want to first check whether the office I'm trying to create already exists or not.
Here is some code from where I've gotten so far, but I'm not able to quite get it right. I would greatly appreciate some input.
CREATE PROCEDURE stored_proc_new_office
AS
BEGIN
DECLARE #office_id int
SELECT #office_id = (SELECT office_id FROM inserted)
IF NOT EXISTS (SELECT 1 FROM OFFICE WHERE office_id = #office_id)
BEGIN
ROLLBACK TRANSACTION
PRINT 'Office already exists.'
END
END
Here is a bare bones example of how you can use a stored procedure to insert a new record with a check to ensure it doesn't already exist.
create procedure dbo.AddNewOffice
(
#Name nvarchar(128)
-- ... add parameters for other office details
, #NewId int out
)
as
begin
set nocount on;
insert into dbo.Office([Name]) -- ... add additional columns
select #Name -- ... add additional parameters to match the columns above
where not exists (select 1 from dbo.Office where [Name] = #Name); -- ... add any additional conditions for testing for uniqueness
-- If nothing inserted return an error code for the calling app to use to display something meaningful to the user
if ##rowcount = 0 return 99;
-- return the new id to the calling app.
set #NewId = scope_identity();
return 0;
end

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 disable trigger when a Cascade On Delete if fired

Let me start out trying to explain… My MSSQL2008 R2 database has two tables, one called SalesHeader the other SalesDetails. If I delete a header record it cascades and deletes all details related to that header. I also have an on delete trigger on the SalesDetails table that renumbers the DetailNumber field for the remaining details.
The Cascade on delete worked before I put in the Trigger on the Details table. The trigger works now but I get an error when trying to delete a header record. I can manually disable the delete trigger on details table and the header delete works as expected.
To fix this I am not quite sure whether a new on delete trigger on the header table that would disable the trigger on the details table would work or if there is a way to detect the Cascade on Delete in the Details trigger so the trigger logic could be bypassed. I have read here on SO that INSTEAD OF DELETE Triggers are not allowed with Cascade on Delete.
Any suggestions would be greatly appreciated and code snippets even more so.
Cheers,
SQL Trigger:
USE [MyDatabase]
GO
/****** Object: Trigger [dbo].[OnDeleteTrigger] Script Date: 9/11/2014 11:51:40 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Randy Gordey
-- Create date: 08/28/2014
-- Description: Renumbers the remaining details
-- when rows are deleted.
-- =============================================
CREATE TRIGGER [dbo].[OnDeleteTrigger]
ON [dbo].[SalesDetail]
AFTER DELETE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
PRINT 'Delete Trigger Fired'
DECLARE #ID int
DECLARE #RowNum int
DECLARE #TransactionNumber int
-- Gets the TransactionNumber from the deleted row
SET #TransactionNumber = (SELECT
intTransactionNumber
FROM DELETED)
DECLARE myCursor CURSOR
/** Gets a collection of the detail lines for this Transaction **/
LOCAL SCROLL STATIC FOR SELECT
intDetailNumber,
ROW_NUMBER() OVER (ORDER BY intDetailNumber) AS 'RowNum'
FROM SalesDetail
WHERE intTransactionNumber = #TransactionNumber;
OPEN myCursor;
-- Grabs the first row
FETCH NEXT FROM myCursor INTO #ID, #RowNum;
-- Begin Loop
WHILE ##FETCH_STATUS = 0 BEGIN
/** Updates Database: Setting new Detail Number for each row. **/
UPDATE SalesDetail
-- intDetailNumber is part of the PrimaryKey but it is not
-- set as an identity, so we can still change it.
SET intDetailNumber = #RowNum
WHERE intDetailNumber = #ID
AND intTransactionNumber = #TransactionNumber;
-- Grabs the next row
FETCH NEXT FROM myCursor INTO #ID, #RowNum;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
END
GO
I found my fix.
SET #Count = (SELECT
COUNT(*)
FROM DELETED)
-- We want to break for multiple rows (ON DELETE CASCADE)
IF (#Count > 1) RETURN
I only delete single rows from the ChildTable at a time. I also want to keep the cascade on delete - as my ChildTable has Children of its own.

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

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.