Trigger running every time despite conditional statement - sql

I am trying to write an update trigger on a table which would cause it to run an additional update statement only if a certain column has been changed, so far the trigger runs the update no matter what, hoping maybe someone can see what I am doing wrong here.
Here is the trigger.
ALTER TRIGGER [dbo].[StatusChangedUpdateTrigger]
ON [dbo].[Trans_Order]
AFTER UPDATE
AS
DECLARE #OldOrderStatusId INT, #NewStatusOrderId INT, #ERRNUM INT;
BEGIN
SET #OldOrderStatusId = (SELECT OrderStatusId FROM deleted);
SET #NewStatusOrderId = (SELECT OrderStatusId FROM inserted);
IF (#OldOrderStatusId != #NewStatusOrderId)
SET NOCOUNT ON;
UPDATE Trans_Order
SET StatusChanged = 1
WHERE Id = (SELECT ID FROM inserted)
END
For some reason this is running no matter what, I can never set StatusChanged to 0 as it will automatically flip it back to 1 even if the OrderStatusId hasn't changed. So my update statement is running no matter what, so I am guessing I am doing something wrong in the if statement.

Hmmmm . . . Your logic seems strange. I would expect:
UPDATE t
SET StatusChanged = 1
FROM Trans_Order t JOIN
Inserted i
ON t.id = i.id JOIN
Deleted d
ON t.id = d.id
WHERE i.OrderStatusId <> d.OrderStatusId;
You might need to take NULL values into account -- although your code does not.
Note that your code is just a bug waiting to happen, because it assumes that inserted and deleted have only one row.
The specific problem with your code is that it is really:
IF (#OldOrderStatusId != #NewStatusOrderId)
BEGIN
SET NOCOUNT ON;
END;
UPDATE Trans_Order
SET StatusChanged = 1
WHERE Id = (SELECT ID FROM inserted);
Your indentation has confused the logic. However, you should still use the set-based version so the trigger does not fail.

The correct way to approach your trigger is as follows:
create or alter trigger [dbo].[StatusChangedUpdateTrigger] on [dbo].[Trans_Order]
after update
as
set nocount on
if ##RowCount=0 return
if Update(OrderStatusId)
begin
update t
set statusChanged=1
from inserted i join deleted d on d.id=i.id and d.OrderStatusId != i.OrderStatusId
join Trans_Order t on t.id=i.id
end
Always test ##rowcount and return if no rows updated.
Always put set options before DML
As you are only looking to update if a specific column is updated you can test specifically for that and if the update statement that's run doesn't touch that column the trigger will not run.
This will correctly account for multiple rows being updated and only update those where the new value is different to the old value.

Related

Trigger : update column based on value of another column

We have a table that contains a status column, and associated to that is a column to track when the status changed values.
As an example, we have a status of 'Off' and a status of 'Found Off' along with associated columns, DateOff and DateFoundOff. I'm trying to create a trigger to update these date columns when the status changes.
It seems rather straightforward to me, but what is occurring is when the Status changes the associated date column updates correctly, but the other date column becomes null. So if I change Status = 'Off' DateOff has the correct date but DateFoundOff becomes null and visa versa.
I created two triggers - first one is:
ALTER TRIGGER [GIS].[UPDATE_FOUNDOFF]
ON [GIS].[METEROUTAGEPOINTS]
AFTER UPDATE
AS
IF (UPDATE (OutageStatus))
BEGIN
SET NOCOUNT ON;
UPDATE [gis].[METEROUTAGEPOINTS]
SET DateFoundOff = CURRENT_TIMESTAMP
FROM gis.METEROUTAGEPOINTS mop
INNER JOIN inserted AS i ON i.ConObject = mop.ConObject
WHERE i.OutageStatus = 'Found Off'
END
And the second
ALTER TRIGGER [GIS].[UPDATE_DATES]
ON [GIS].[METEROUTAGEPOINTS]
AFTER UPDATE
AS
IF (UPDATE (OutageStatus))
BEGIN
SET NOCOUNT ON;
UPDATE [gis].[METEROUTAGEPOINTS]
SET DateOff = CURRENT_TIMESTAMP
FROM gis.METEROUTAGEPOINTS mop
INNER JOIN inserted AS i ON i.ConObject = mop.ConObject
WHERE i.OutageStatus = 'Off'
END
I simply do not understand how one trigger is changing the value of the Date column to null that is not associated to the current status value.
Thanks.
Edit: The issue was found to not be with trigger but instead be with how the tool being used to edit the data was holding onto something. Not sure I understand why, but by changing the edit workflow the problem was resolved. Marked answer as correct based on it giving a far better way to write the trigger
Neither of your update statements change DateFoundOff or DateOff to null. Something else must be going on.
However I would improve your trigger as follows:
Only use a single trigger, every trigger has overhead, having one trigger with one update statement will run faster than 2.
You aren't actually checking that the status has changed, all you are checking is that the update included that column. Add a check against the deleted table actually checks whether the value changed.
ALTER TRIGGER [GIS].[UPDATE_FOUNDOFF]
ON [GIS].[METEROUTAGEPOINTS]
AFTER UPDATE
AS
BEGIN
-- Avoid doing any processing if no rows are updated
IF NOT EXISTS (SELECT 1 FROM Inserted) RETURN;
SET NOCOUNT ON;
IF UPDATE(OutageStatus) BEGIN
UPDATE [gis].[METEROUTAGEPOINTS] SET
DateFoundOff = CASE WHEN i.OutageStatus = 'Found Off' AND d.OutageStatus <> 'Found Off' THEN CURRENT_TIMESTAMP ELSE DateFoundOff END
, DateOff = CASE WHEN i.OutageStatus = 'Off' AND d.OutageStatus <> 'Off' THEN CURRENT_TIMESTAMP ELSE DateOff END
FROM gis.METEROUTAGEPOINTS mop
INNER JOIN inserted AS i ON i.ConObject = mop.ConObject
INNER JOIN deleted AS d ON d.ConObject = mop.ConObject;
END;
END;

Update Trigger Appends data twice to column

O.F.,
So I have been attempting to build a trigger that will update a table based on the when a a row is updated in a different table. The trigger so far looks like this.
ALTER TRIGGER [dbo].[tst_update_USCATVLS_6]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM dbo.IV00101))
UPDATE dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
What seems to happen when I run the test update like the one below.
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
Is that the trigger fires but updates the desired column twice. The end result being this 20025947756319_0_0 instead of this 20025947756319_0.
The weird part of all of this is if I drop the trigger and run the same test update and then run the update statement that was in the trigger all statements execute and the data is updated as desired.
So running this as one block of code works:
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM PDM.TEST.dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM PDM.TEST.dbo.IV00101))
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
If any one can help me figure out why this is happening I would greatly appreciate it.
Kindest regards,
Z.
Having read Sean Lange's Comments I looked up inserted and deleted tables. I saw reference to these tables before but didn't realize they were temporary tables and thought they were physical tables of the database in the example/answers I saw. Anyway I created a trigger to show the data in each which helped me see how to join them to the update statement in the trigger. All the code is below.
Trigger to see what was in Inserted and Deleted as well as the join referencing the Inserted table:
ALTER TRIGGER [dbo].[inserted_deleted_Table]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
select 'NEW DATA', * from inserted --new data
select 'OLD Data', * from deleted --old data
select 'iv00101', * From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS
-- Insert statements for trigger here
END
The end solution was to remove this peice of code:
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM INV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM INV00101 ) AND USCATVLS_6 ='OBSOLETE' )
And then Add a the FROM clause to the UPDATE Statement:
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0'
FROM (
SELECT u.ITEMNMBR ,
u.DEX_ROW_TS
From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS) as p
WHERE PT_UD_KEY = p.ITEMNMBR AND PT_UD_Number = 2
Once I figured out that insert and deleted table were temp tables not actual tables in a DB all the pieces sorta fell into place. Thanks for pointing me in the direction I needed to go #Sean Lange.
Best Regards,
Z.

Performance issues with UPDATE in AFTER UPDATE Trigger

I have a small performance issue with one of my database triggers in my MS-SQL Server 2014 database.
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE T_TPM_Vehicle SET LastUpdated = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted)
UPDATE T_TPM_Vehicle SET [DisturbedSince] = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
INSERT INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
,[Pos_X]
,[Pos_Y]
,[Alpha]
,[LastAutoPos_X]
,[LastAutoPos_Y]
,[LastAutoAlpha]
,[Automatic]
,[Manual]
,[Blocked]
,[Loaded]
,[Stoped]
,[Emergency_Stop]
,[User_Required]
,[BatteryAlmostEmpty]
,[BatteryEmpty]
,[BatteryLevel]
,[ChargingRelaisEnable]
,[NavOK]
,[PowerOn]
,[Available]
,[OperatingMinutes]
,[UpdateOperatingMinutes]
,[DataChangedByVIS]
,[Blockingsreleased]
,[Cancelled]
,[ProductID]
,[HUIdent1]
,[HUIdent2]
,[HUType]
,[DisturbedSince])
SELECT inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
,inserted.[Pos_X]
,inserted.[Pos_Y]
,inserted.[Alpha]
,inserted.[LastAutoPos_X]
,inserted.[LastAutoPos_Y]
,inserted.[LastAutoAlpha]
,inserted.[Automatic]
,inserted.[Manual]
,inserted.[Blocked]
,inserted.[Loaded]
,inserted.[Stoped]
,inserted.[Emergency_Stop]
,inserted.[User_Required]
,inserted.[BatteryAlmostEmpty]
,inserted.[BatteryEmpty]
,inserted.[BatteryLevel]
,inserted.[ChargingRelaisEnable]
,inserted.[NavOK]
,inserted.[PowerOn]
,inserted.[Available]
,inserted.[OperatingMinutes]
,inserted.[UpdateOperatingMinutes]
,inserted.[DataChangedByVIS]
,inserted.[Blockingsreleased]
,inserted.[Cancelled]
,inserted.[ProductID]
,inserted.[HUIdent1]
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
FROM inserted
END
What it basically does is it sets the LastUpdated column for all rows in inserted and the DisturbedSince column for a subset of the inserted rows.
Finally the inserted rows get copied to a history table. (Every change on any row must be saved for two days). Older data gets deleted by a maintenance job.
As we have up to ~ 300 rows updated per second (Updates to rows can be batched together) We create a big amount of data and recursive updates.
I've now found the INSTEAD OF UPDATE triggers which seem to solve the recursive UPDATE problem caused by my trigger but I would have to process every row of the inserted table one by one with an update statement in the trigger.
I'm not sure if this is really faster. Does anyone of you have a recommendation?
What I really need is to tweak / extend the data rows before they are send to the table. Is there an approach for this?
e.g.: Something like:
CREATE TRIGGER ... INSTEAD OF UPDATE
AS
BEGIN
UPDATE inserted SET LastUpdated = getdate()
UPDATE inserted SET DisturbedSince
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
"SAVE INSERTED"
END
and an AFTER UPDATE TRIGGER with the storage of the changed data to the history table.
Thank you for any suggestions.
Thomas
You're right to think that using an INSTEAD OF trigger is the right way to go rather than an AFTER trigger, when you're wanting to change data within the same table as well.
It would be something like:
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
INSTEAD OF UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE tgt
SET
Vehicle_Ref = i.Vehicle_Ref,
Vehicle_Type = i.Vehicle_Type,
...
LastUpdated = getdate(),
DisturbedSince = CASE WHEN i.Emergency_Stop=1 and d.Emergency_Stop=0
THEN getdate() ELSE d.DisturbedSince END
OUTPUT
inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
...
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
...
,[HUIdent2]
,[HUType]
,[DisturbedSince])
FROM
T_TPM_Vehcile tgt
inner join
inserted i
on
tgt.Vehicle_Number = i.Vehicle_Number
inner join
deleted d
on
tgt.Vehicle_Number = d.Vehicle_Number
You'll note that I've combined both the UPDATEs and the INSERT into the history table into a single compound statement.
You'll also note that it's slightly confusing because there are two inserteds in play here - the inserted as part of the trigger (aliased as i to sidestep some of the confusion) and the inserted as part of the OUTPUT clause.

INSERT Trigger not firing an update?

Ok so i have a fairly basic trigger :
In words, After Insert, Get IP information and update the Inserted row with the new data
CREATE TRIGGER [BasicData.IPInfo.Gather]
ON [BasicData]
AFTER INSERT
AS
BEGIN
DECLARE #City VARCHAR(1000),
#Country VARCHAR(1000),
#IP VARCHAR(1000),
#ROWID UNIQUEIDENTIFIER
SELECT #IP=[IP],#ROWID=[ID] FROM [inserted]
SELECT #Country = [Country], #City= [City]
FROM [IPInfo] WHERE [IP] = #IP
IF (#City IS NOT NULL) AND (#Country IS NOT NULL)
BEGIN -- Never seems to fire
UPDATE [BasicData]
SET [IPCountry]=#Country,[IPCity]=#City
WHERE [ID] = #ROWID
END
ELSE
BEGIN -- Fired correctly
INSERT INTO [IPInfo.Missing] VALUES (#IP)
END
END
Now the problem is, It adds the missing IP information correctly (Only when missing), however, it does not seem to ever update the table when it does, What am i missing?
Ive tweaked it in every possible way i could think of... (My trigger knowledge is rather bad)
There are a few problems:
INSERTED can contain many rows. Your trigger allows for only one.
The trigger is AFTER INSERT, and I guess this might exclude UPDATEs. Try AFTER INSERT, UPDATE
Your IF statement isn't checking for an UPDATE - what if the column is UPDATED to NULL? it won't catch it even though it was an update. What if something inserted NON NULL data? it will think it was an UPDATE.
As far as I know the only way to identify an UPDATE is to join INSERTED and `DELETED on the PK. If there is a match, it's been updated.
Perhaps you could rewrite it like this:
CREATE TRIGGER [BasicData.IPInfo.Gather]
ON [BasicData]
AFTER INSERT, UPDATE
AS
BEGIN
-- Save UPDATES to BasicData
UPDATE [BasicData]
SET [IPCountry]=I.Country,[IPCity]=I.City
FROM [BasicData] UT
INNER JOIN
[inserted] I
ON I.ID = UT.ID
INNER JOIN
Deleted D
ON D.ID = I.ID
INNER JOIN
[IPInfo] IP
ON I.ID = IP.ID
-- Save inserts to Missing
INSERT INTO [IPInfo.Missing] (IP)
SELECT IP FROM
INSERTED I
WHERE NOT EXISTS (SELECT 1 FROM DELETED D WHERE D.ID = I.ID)
END

Prevent trigger from firing

I have the following trigger
First trigger:
ALTER TRIGGER [dbo].[DIENSTLEISTUNG_Update]
ON [dbo].[DIENSTLEISTUNG]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #intNewID int
INSERT INTO [DIENSTLEISTUNG]
(DESCRIPTION, QUANTITY,
PRICE, AZ_MO, AZ_DI,AZ_MI,AZ_DO,AZ_FR,
AZ_SA,AZ_SO,DIENSTLEISTUNGSART_ID,
UPDATE_USER, UPDATE_DATE,
PERMISSIONS, KONTRAKTPOSITION,ITEMNUMBER,
PRIORITY, VALID)
SELECT i.DESCRIPTION, i.QUANTITY, i.PRICE, i.AZ_MO,
i.AZ_DI,i.AZ_MI,i.AZ_DO,i.AZ_FR,
i.AZ_SA,i.AZ_SO,i.SERVICETYPE_ID, i.UPDATE_USER,GETDATE(),
i.PERMISSIONS, i.KONTRAKTPOSITION,i.ITEMNUMBER, i.PRIORITY, 'Y'
FROM INSERTED i
JOIN deleted d ON i.ID=d.ID
WHERE i.PRICE<>d.PRICE
or i.DESCRIPTION<>d.DESCRIPTION
IF ( UPDATE (PRICE) OR UPDATE (DESCRIPTION) )
UPDATE S
SET s.VALID = 'N'
FROM SERVICE s
JOIN INSERTED i ON I.ID = S.ID
IF UPDATE(PRIORITY)
UPDATE s
SET s.PRIORITY= i.PRIORITY
FROM SERVICE s
JOIN INSERTED i ON i.ID = s.ID
SET NOCOUNT OFF;
END
The first Trigger copies an entire row with a new ID if a change in the original row happens, also the trigger set a flag. The old row gets the flag VALID = 'N' and the new row gets the flag VALID = 'Y'. The trigger only creates a new row if PRICE or DESCRIPTION are updated. So far so good.
My problem is that if I want to update the PRIORITY in the new row the trigger fires again and sets the flag to VALID = 'N'. That should not happen. I want only to update the priority without creating a new row or update a another column.
Thanks for help
You cannot prevent a trigger from firing - if it's present and not disabled, it will fire. That's how triggers work.
What you can do is check inside your trigger which columns have been updated. So you could do something like this in your one single trigger:
CREATE TRIGGER [dbo].[DIENSTLEISTUNG_Update]
ON [dbo].[DIENSTLEISTUNG]
FOR UPDATE
AS
IF UPDATE(PRICE)
... (do what you need to do if PRICE is updated)...
IF UPDATE(DESCRIPTION)
... (do what you need to do if DESCRIPTION is updated)...
IF UPDATE(PRIORITY)
... (do what you need to do if PRIORITY is updated)...
Use the UPDATE() function to check whether a given column has been updated - and if so, act on it. See the MSDN docs on how to use the UPDATE() function.
You can make triggers fire only on certain columns or one colomn.
like this.
CREATE TRIGGER tr_something ON myTable
FOR INSERT, UPDATE
AS
IF UPDATE(myColumn)
BEGIN
-- do what you want
END
post below gives more details, seems I'm to slow :)
What you can do is set the context info of the session which you are in like this:
SET Context_Info 0x55555
And then in your trigger check for the context info to decide what to do:
DECLARE #Cinfo VARBINARY(128)
SELECT #Cinfo = Context_Info()
IF #Cinfo = 0x55555
RETURN