SQL Server trigger interfering with select statement - sql

We have a trigger on a table that fires during an update. We use Entity Framework on the front end. So, we don't want to see the intermediate results (the results from the trigger) when we do an update. I have SET NOCOUNT ON in my trigger:
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
But when we execute the update, we see the intermediate results first and that throws Entity Framework.
Here is the sql command:
Declare #UserID int
set #UserID = 26
declare #WorkItemID int
Declare #UserRoles TABLE ( roleid INT ,parentroleid INT ,rolename VARCHAR(100) )
INSERT INTO #UserRoles SELECT R.id,R.parentroleid,R.ROLE FROM userroles UR
INNER JOIN roles R ON UR.roleid = R.id WHERE UR.userid = #UserID
Update MedCMDWorkItems with (UPDLOCK) Set AssignedTo = #UserID, AssignedAt =
getdate(), LastUpdatedAt = getdate(), LastUpdatedBy = #UserID, #WorkItemID =
ID
where ID = (SELECT TOP 1 PR.ID FROM MedCMDWorkItems PR --with (UPDLOCK)
JOIN TaskTypes TT on PR.TaskTypeID = TT.ID WHERE PR.AssignedTo IS NULL AND
PR.ClosedAt IS NULL
AND Exists (SELECT 1 FROM #UserRoles WHERE roleid in (852,772)) AND
((PR.WorkItemTypeID = (select 29 FROM #UserRoles UR WHERE UR.RoleID = 852)
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename)
+ 1, Len(UR1.rolename)) FROM #UserRoles UR1 WHERE UR1.parentroleid in (852))
) OR
(PR.WorkItemTypeID = (select 28 FROM #UserRoles UR WHERE UR.RoleID = 772)
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename)
+ 1, Len(UR1.rolename)) FROM #UserRoles UR1 WHERE UR1.parentroleid in (772))
) )
ORDER BY (CASE WHEN PR.IsUrgent IS NULL THEN 'False' ELSE PR.IsUrgent END)
DESC, ( CASE WHEN PR.Priority IS NULL THEN 9999 ELSE PR.Priority END ),
PR.CreatedAt)
Select * From MedCMDWorkItems PRI Where ID = #WorkItemID
Here is the trigger:
USE [CombinedWorkflow]
GO
/****** Object: Trigger [dbo].[trgForMedCMDWorkItemsUpdate] Script Date:
4/3/2018 5:20:22 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare #NewAssignedAt DateTime;
declare #OldAssignedAt DateTime;
declare #NewPulledAt DateTime;
declare #ID int;
select #NewPulledAt = i.PulledAt from inserted i;
select #NewAssignedAt = i.AssignedAt from inserted i;
select #OldAssignedAt = d.AssignedAt from deleted d;
select #ID = i.ID from inserted i;
--If work item is being un-assigned
if(#OldAssignedAt is NOT NULL and #NewAssignedAt is NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set PulledAt = NULL
Where ID = #ID
END
--if work item is being assinged
ELSE if(#NewAssignedAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewAssignedAt
Where FirstAssignedAt is NULL and ID = #ID
END
-- if work item is being pulled
ELSE if(#NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set AssignedAt = #NewPulledAt
Where AssignedAt is NULL and ID = #ID
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewPulledAt
Where FirstAssignedAt is NULL and ID = #ID
END
Select 1
END
When I execute in SSMS with the trigger on under results, I get 2 results, first indicating 1 row was updated, then a second indicating the results of the query.
If I disable the trigger and execute the command I don't get the first result, and that is my desired behavior with the trigger enabled.
How do I set this trigger up so it fires but does not cause the additional results?
Thanks,
Sammer

you need to use inserted and deleted as tables, they can hold more than one row so you never can fill a variable from it.
In stead you have to build it like this example :
your code (first part of it)
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE AS
BEGIN
SET NOCOUNT ON;
declare #NewAssignedAt DateTime;
declare #OldAssignedAt DateTime;
declare #NewPulledAt DateTime;
declare #ID int;
select #NewPulledAt = i.PulledAt from inserted i;
select #NewAssignedAt = i.AssignedAt from inserted i;
select #OldAssignedAt = d.AssignedAt from deleted d;
select #ID = i.ID from inserted i;
--If work item is being un-assigned
if(#OldAssignedAt is NOT NULL and #NewAssignedAt is NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set PulledAt = NULL
Where ID = #ID
END
can be replaced by something like this (not tested because i dont have your database)
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE AS
BEGIN
SET NOCOUNT ON;
update MedCMDWorkItems
Set PulledAt = NULL
where ID in ( select i.Id
from Inserted i
left join deleted d on i.Id = d.Id
where d.AssignedAt is not null
and i.AssignedAt is null
)
Once you get this it will not be so hard to figure out how to adjust the rest of your trigger

Thanks everyone for the input. I figured out the issue. The trigger had this at the end:
...
END
-- if work item is being pulled
ELSE if(#NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set AssignedAt = #NewPulledAt
Where AssignedAt is NULL and ID = #ID
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewPulledAt
Where FirstAssignedAt is NULL and ID = #ID
END
Select 1
END
No idea what that last line (Select 1) is supposed to be for (I didn't write the trigger :)), but it was causing the unwanted results being displayed.
Sammer

Related

Stored procedure. Check if constraint are used in another table

I get this error message:
The DELETE statement conflicted with the REFERENCE constraint
"FK_FieldMapper_Field". The conflict occurred in database "SCAM",
table "dbo.FieldMapper", column 'FieldID'.
I have some accesshelpers which has a given amount of fields. These Fields can be used by multiple accesshelpers..
When I delete an accesshelper, I need to check if the fields in the given accesshelper are used by other accesshelpers. If they are, I shall delete the accesshelper, but not the fields, as that would break other accesshelpers.
How do i do that?
This is what I have come up with on my own so far.
USE [SCAM]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #projectIds TABLE (id int);
INSERT INTO #projectIds ([id]) SELECT dbo.AccessHelperMapper.ProjectID FROM dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DELETE dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DECLARE #fieldIds TABLE (id int);
INSERT INTO #fieldIds ([id]) SELECT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID = #Id;
DECLARE #AHfields TABLE (id int)
Insert into #AHfields
Select fids.id from dbo.FieldMapper, #fieldIds as fids
where dbo.FieldMapper.AccessHelperID != #Id
and fids.id != dbo.FieldMapper.FieldID;
delete dbo.FieldMapper where dbo.FieldMapper.AccessHelperID = #Id and dbo.FieldMapper.FieldID IN (SELECT d.id FROM #AHfields as d);
delete dbo.Field where dbo.Field.ID in (SELECT g.id FROM #AHfields as g);
delete dbo.AccessHelper where dbo.AccessHelper.ID = #Id;
END
Try the below code. Hope this is what you were looking for:
USE [SCAM]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DELETE dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DECLARE #fieldIds TABLE (id int);
INSERT INTO #fieldIds ([id]) SELECT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID = #Id
AND dbo.FieldMapper.FieldID NOT IN (SELECT DISTINCT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID != #Id);
delete dbo.FieldMapper where dbo.FieldMapper.AccessHelperID = #Id --and dbo.FieldMapper.FieldID IN (SELECT d.id FROM #fieldIds as d);
delete dbo.Field where dbo.Field.ID in (SELECT g.id FROM #fieldIds as g);
delete dbo.AccessHelper where dbo.AccessHelper.ID = #Id;
END
First of all you need a transaction because you have partial data changes which cannot be applied separately.
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #fieldIds TABLE (id int);
BEGIN TRY
BEGIN TRAN
DELETE ahm
FROM dbo.AccessHelperMapper ahm
WHERE ahm.AccessHelperID = #Id;
DELETE fm
OUTPUT DELETED.fieldID
INTO #fieldIds(id)
FROM dbo.FieldMapper fm
WHERE fm.AccessHelperID = #Id
DELETE f
FROM dbo.Field f
WHERE exists(select 1 from #fieldIDs fid where fid.ID = f.ID)
and not exists(
select 1 from dbo.FieldMapper fm
where fm.fieldID = f.ID
and fm.AccessHelperID != #ID -- redundant, may be removed.
)
DELETE ah
FROM dbo.AccessHelper ah
where ah.ID = #Id
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() IN (1, -1)
ROLLBACK TRAN
THROW;
END CATCH
END

Azure SQL Database trigger is not running and I cannot seem to figure out why

I'm trying to update the status column of a table when the table has been edited. I use a similar trigger on another table to update this status but when I try to put the trigger on the table itself it will not run.
ALTER TRIGGER dbo.tr_Invoice_SetInvoiceStatus_Update
ON dbo.Invoice
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #InvoiceID int
DECLARE #TotalInvoice DECIMAL(18,2)
DECLARE #TotalPayments DECIMAL(18,2)
DECLARE #InvoiceStatus NVARCHAR(50)
DECLARE #InvoiceStatusID INT
SELECT #InvoiceID = ID
FROM Inserted i
SELECT #TotalPayments = IsNull(Sum(ph.Amount), 0)
FROM PaymentHistory ph
WHERE InvoiceID = #InvoiceID
SELECT #TotalInvoice = ISNULL(Total, 0)
FROM Invoice
WHERE ID = #InvoiceID
IF (#TotalPayments > 0)
BEGIN
IF (#TotalPayments >= #TotalInvoice)
BEGIN
SELECT #InvoiceStatus = 'Paid'
END
ELSE
BEGIN
SELECT #InvoiceStatus = 'Partially Paid'
END
END
ELSE
BEGIN
SELECT #InvoiceStatus = 'Open'
END
SELECT #InvoiceStatusID = ID
FROM dbo.InvoiceStatus
WHERE [Name] = #InvoiceStatus
UPDATE dbo.Invoice
SET InvoiceStatusID = #InvoiceStatusID
WHERE ID = #InvoiceID
END
GO
Any help would be great?

update trigger get the updated primary key

I have a trigger ,but I need to get the updated record's primary key (like as inserting the data SELECT #Id= ##IDENTITY) thus, I can pass it to where condition. How can I do that?
ALTER TRIGGER [dbo].[CariBakiyeBorcAktar]
ON [dbo].[BakimKartiDegisenParcalar]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Id int
DECLARE #CariId int
DECLARE #SId int
DECLARE #MId int
declare #Tutar decimal
declare #Bakiye decimal
declare #s decimal = 0
DECLARE #ParcaId int
--how I can I get the last updateed record Identity like this??
--and pass it to update query as a where condition
SELECT #Id= ##IDENTITY
set #SId=(select SId from CariBakiye where Id =#Id)
select #CariId=tblk.CariId ,#MId=tblk.MId, #SId= tblk.SId,#Tutar=tblk.Tutar from (
SELECT tbl.CariId , tbl.MId,tbl.SId,tbl.Tutar from (select cb.MId,SUM(bk.Tutar) as Tutar,bk.SId,cb.Id as CariId FROM [BakimKartiDegisenParcalar] bk
join CariBakiye cb on cb.SId=bk.SId
where bk.SId =cb.SId group by bk.SId,cb.MId,cb.Id ) as tbl
) as tblk where SId = #SId
set #Bakiye = #s-#Tutar
update CariBakiye set Borc=#Tutar,Bakiye=#Bakiye where Id=#CariId
print #Id
-- Insert statements for trigger here
END
As Martin said, you have to understand that SQL Server triggers are per statement, not per row. So in context of your trigger you have two tables - inserted and deleted, where you could find all information about data updated. If you really want to do per row processing, you could use cursor:
ALTER TRIGGER [dbo].[CariBakiyeBorcAktar] ON [dbo].[BakimKartiDegisenParcalar]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Id int
DECLARE #CariId int
DECLARE #SId int
DECLARE #MId int
declare #Tutar decimal
declare #Bakiye decimal
declare #s decimal = 0
DECLARE #ParcaId int
declare tr_cursor cursor local fast_forward for
select ID from inserted
while 1 = 1
begin
fetch tr_cursor into #Id
if ##fetch_status <> 0 break
set #SId=(select SId from CariBakiye where Id =#Id)
select #CariId=tblk.CariId ,#MId=tblk.MId, #SId= tblk.SId,#Tutar=tblk.Tutar from (
SELECT tbl.CariId , tbl.MId,tbl.SId,tbl.Tutar from (select cb.MId,SUM(bk.Tutar) as Tutar,bk.SId,cb.Id as CariId FROM [BakimKartiDegisenParcalar] bk
join CariBakiye cb on cb.SId=bk.SId
where bk.SId =cb.SId group by bk.SId,cb.MId,cb.Id ) as tbl
) as tblk where SId = #SId
set #Bakiye = #s-#Tutar
update CariBakiye set Borc=#Tutar,Bakiye=#Bakiye where Id=#CariId
print #Id
-- Insert statements for trigger here
end
close tr_cursor
deallocate tr_cursor
END

SQL Update trigger doesn't work as expected

I have a problem with the programming of an Update trigger. I want to create a trigger that copies the row I edited in a new row with the new data and a new ID. The old row should be the same with just a flag change from 0 to 1.
The table look like this:
ID Artikelname PREIS UPDATE_DATE FLAG
1 Tomatoe 3 14.06.2012 16:00 0
2 Apple 1,5 12.05.2012 14:45 0
When I change the price of the first row, the table should look like this:
ID Artikelname PREIS UPDATE_DATE FLAG
1 Tomatoe 3 14.06.2012 16:00 1
2 Apple 1,5 12.05.2012 14:45 0
1 Tomatoe 2 13.07.2012 10:45 0
Here is my trigger so far:
USE [TestDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Produkt_Update]
ON [dbo].[Produkt]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #ID int
DECLARE #Artikelname nvarchar(50)
DECLARE #Preis numeric
DECLARE #Flag numeric
DECLARE #max_id int
SET #ID = (SELECT ID FROM inserted)
SET #Artikelname = (SELECT Artikelname FROM inserted)
SET #Preis = (SELECT Preis FROM inserted)
SET #Flag = (SELECT Flag FROM inserted)
SET #max_id = (SELECT MAX(ID) from dbo.Produkt)
SET IDENTITY_INSERT dbo.Produkt ON
INSERT INTO dbo.Produkt
(ID,Artikelname,Preis)
values (#max_id+1,#Artikelname,#Preis)
SET IDENTITY_INSERT dbo.Produkt OFF
UPDATE dbo.Produkt
SET Flag = 1
WHERE ID=#ID
END
With my trigger I can create the new row but the price on the first row sill changes. I don't know how to handle this. Could you help me please?
Please try this and let me know
CREATE TRIGGER [dbo].[Produkt_Update]
ON [dbo].[Produkt]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MAX_ID INT;
SELECT #MAX_ID=MAX(ID) FROM [Produkt];
declare #tmp Table(ID int, Artikelname varchar(200),
PREIS varchar(200),UPDATE_DATE datetime, FLAG bit)
insert into #tmp
select ID,Artikelname,PREIS,UPDATE_DATE,1 [flag] from deleted;
delete T from [Produkt] T JOIN #tmp I
ON T.ID=I.ID
SET IDENTITY_INSERT [Produkt] ON
INSERT INTO [Produkt] (ID,Artikelname,PREIS,UPDATE_DATE,FLAG)
SELECT #MAX_ID+ROW_NUMBER() OVER(ORDER BY ID) [ID],Artikelname,PREIS,GETDATE(),0
FROM INSERTED
union all
select * from #tmp
SET IDENTITY_INSERT dbo.Produkt OFF
SET NOCOUNT OFF;
END;
You need to insert the price (preis) from the deleted table, not the inserted table
Replace
SET #ID = (SELECT ID FROM inserted)
with
SET #ID = (SELECT ID FROM deleted)

Is there a way to force a trigger to run on an update statement with multiple rows?

I have had to make changes to a trigger and assumed that running an update query like the following would make the trigger execute for all the matched rows. But instead, it only updates the record that it finds.
UPDATE someTable SET someField = someField WHERE someField = 'something';
As a quick solution, I created the following query using a cursor to loop through the records and update each row. It works, and luckily I don't have a really large dataset so it doens't take too long, but it just doesn't seem like the best solution.
DECLARE #id INT;
DECLARE queryCursor CURSOR FOR
SELECT id FROM someTable WHERE someField='something'
OPEN queryCursor
FETCH NEXT FROM queryCursor INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE someTable SET someField = someField WHERE id = #id
FETCH NEXT FROM queryCursor INTO #id
END
CLOSE queryCursor
DEALLOCATE queryCursor
Is there a better way to get a trigger to execute on multiple rows in SQL Server?
Edit: The code from trigger
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE CommonData SET ReportedLineNo = #LN WHERE CommonDataLink = #CDL
End
You have to make use of the special table INSERTED for what you want:
UPDATED CODE
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE A
SET ReportedLineNo = B.LineService + B.CommonDataLink
FROM CommonData A
INNER JOIN INSERTED B
ON A.CommonDataLink = B.CommonDataLink
End