Creating an Update Trigger with Where Filter - sql

i'm new at creating triggers. I'm attempting to create a trigger which updates a the tblServiceOrders.GeneralSymptoms with tblAccounts.HotNote where tblAccounts.Number = tblServiceOrders.AccountNumber and where whenever a new line is added to tblServiceOrders. Here is what I've got so far.
ALTER TRIGGER [dbo].[HOTNOTE_update] ON [dbo].[tblServiceOrders]
AFTER INSERT
AS
BEGIN
UPDATE tblServiceOrders tblAccounts.AccountNumber = tblServiceOrders.AccountNumber
SET GeneralSymptoms =
(
SELECT HotNote FROM tblAccounts, tblServiceOrders
WHERE tblAccounts.AccountNumber = tblServiceOrders.AccountNumber
)
FROM tblServiceOrders
WHERE tblServiceOrders.SOType = 'BE Maintenance' OR tblServiceOrders.SOType = 'DD Maintenance'
END

Reading your code, every time you insert a record, you will update the whole table, that is really high cost. I guess you don't really want to do that. If you want to just update the record you just insert, then why not before insert make the data ready and insert directly.

Assuming you're using SQL Server, it looks like your UPDATE statement is trying to perform the following:
UPDATE so
SET so.GeneralSymptoms = a.hotnote
FROM tblServiceOrders so
JOIN tblAccounts a ON so.AccountNumber = a.AccountNumber
WHERE so.SOType = 'BE Maintenance'
OR so.SOType = 'DD Maintenance'
This will update all records in the table with these matching criteria. You might need to consider using just the Inserted record -- depends on your DB Structure.

Related

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.

SQL Server 2014 : FileTable trigger on update File_Stream.PathName()

I have a FileTable FT and another table AT. In AT, I have extended metadata properties of the file in FT.
I have tried to create a trigger ON FT FOR UPDATE that will update the file path that is in AT.
Here is what I've tried:
ALTER TRIGGER [dbo].[u_filepath]
ON [FileDB].[dbo].[FT]
FOR UPDATE
AS
Declare #newpath nvarchar(1000)
Declare #oldpath nvarchar(1000)
select #newpath = file_stream.pathname()
from inserted
select #oldpath = file_stream.pathname()
from deleted
update AT
set [Path] = #newpath
where [Path] = #oldpath
GO
When I execute the query, it spins. I'm planning on leaving it running overnight just in case it decides to do something.
I want the Path column in AT to update to the updated file_stream.PathName() from FT.
Is the trigger logical?
Should I store the file_stream BLOB in my AT Path column instead of the actual path?
Your trigger has MAJOR flaw in that you seem to assume it'll be called once per row - that is not the case.
The trigger will fire once per statement, so if your UPDATE statement that causes this trigger to fire inserts 25 rows, you'll get the trigger fired once, but then the Deleted and Inserted pseudo tables will each contain 25 rows.
Which of those 25 rows will your code select here?
select #newpath = file_stream.pathname()
from inserted
It's non-deterministic, you'll get one arbitrary row and you will be ignoring all other rows.
You need to rewrite your trigger to take this into account - use a set-based approach to updating your data - not a row-based one - something like this:
ALTER TRIGGER [dbo].[u_filepath]
ON [FileDB].[dbo].[FT]
FOR UPDATE
AS
-- create a CTE to get the primary key, old and new path names
WITH UpdatedPaths AS
(
SELECT
i.PrimaryKey,
OldPathName = d.file_stream.pathname(),
NewPathName = i.file_stream.pathname()
FROM
Inserted i
INNER JOIN
Deleted d ON i.PrimaryKey = d.PrimaryKey
)
-- update the "AT" table where the old and new path names don't match
UPDATE dbo.AT
SET [Path] = up.NewPathName
FROM UpdatedPaths up
WHERE
up.OldPathName <> up.NewPathName
dbo.AT.PrimaryKey = up.PrimaryKey
GO

Creating a trigger to update multiple records after insert sql server 2008

Well basically I need this trigger to work after a user inserts multiple records into the database. So that when an optionID of 0 is inserted and the IsoptionalID = 1, then set the OptionID = NULL
CREATE TRIGGER ThisDatabase
ON OtherTable
AFTER INSERT
AS
BEGIN
DECLARE #OPTIONID INT
SET #OPTIONID = OtherTable.OPTIONID
DECLARE #ISoptional INT
SET #ISoptional = OtherTable.ISoptional
CASE #optionID WHEN 0 and #ISoptional = 1 set update OtherTable set optionid = null end
END
I am not sure about the case itself either.
Thank you in advance
This depends on the key field(s) of the table, but SQL Server triggers always work on the entire data set being modified (Inserted, Updated, or Deleted). So the trigger would something more like:
CREATE TRIGGER ThisDatabase
ON OtherTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
UPDATE ot
SET ot.OptionID = NULL
FROM OtherTable ot
INNER JOIN INSERTED ins
ON ins.KeyField = ot.KeyField
WHERE ins.OptionID = 0
AND ins.IsOptional = 1
END
The INSERTED table has the rows that were either Inserted or Updated (current version).
The DELETED table has the rows that were either Deleted or Updated (old version).
So, the INSERTED and DELETED tables are pre-filtered to only the changed records, but they are not updatable (since the event already happened due to this being an AFTER trigger and SQL Server not having a BEFORE trigger) so you need to do the UPDATE on the real table.
It isn't really clear what you want to do, but here's a skeleton. Just note:
Triggers are created on the table which is being affected (not an Other table)
You can certainly update another table as a consequence of a trigger. This is typically done through a join.
Use the inserted and deleted pseudo-tables to identify the record(s) which have been inserted, updated or deleted.
CREATE TRIGGER TR_TableBeingInsertedInto
ON TableBeingInsertedInto
AFTER INSERT
AS
BEGIN
UPDATE OtherTable
-- What you actually want to do here isn't clear to me
SET OtherTable.OPTIONID =
CASE i.OptionID
WHEN 0 THEN NULL
ELSE OtherTable.OPTIONID
END
FROM OtherTable
-- Inserted has the same schema as TableBeingInsertedInto
INNER JOIN INSERTED i
ON OtherTable.SomeCommonKey = i.SomeCommonKey;
END

How to refer to "New", "Old" row for Triggers in SQL server?

SO I'm new to SQL server from SQLite and I am used to using the New|Old keywords. I have seen that some people use the inserted value refer to a newly created row, but this would only apply on an insert and not an update. Howe can I get something like the New I use in this query?
create trigger ports_country_id_in_check
on [SISTEMA].Puerto
after insert, update
AS
BEGIN
update [SISTEMA].Puerto
set country_id = (select secuencia from [SISTEMA].Pais where codigo = New.pais_asociado)
where [SISTEMA].Puerto.secuencia = New.secuencia
end
Inserted also will also apply to update. One updated row will be seen as a deleted and an inserted row. So you can check both what was and what it is now.
create trigger ports_country_id_in_check
on [SISTEMA].Puerto
after insert, update
AS
BEGIN
Declare #pais_asociado, #secuencia int
select #pais_asociado = Puerto.pais_asociado, #secuencia = Puerto.secuencia
from Puerto join inserted
where Puerto.secuencia = inserted.secuencia
update [SISTEMA].Puerto
set country_id = (select secuencia from [SISTEMA].Pais where codigo = #pais_asociado)
where [SISTEMA].Puerto.secuencia = #secuencia
end

Executing a trigger; updating another field

I have been reading up on triggers, and I don't seem to be finding an example, that handles my situation. My situation is unfortunate. The previous DBA scattered redundant data throughout our database.
I'd like to update the company name in multiple other tables once the company name has been changed in my organiz table. I have this, but it doesn't seem to work:
CREATE TRIGGER updOrgNameTrigger
ON organiz
AFTER UPDATE
AS
IF UPDATE(Org_Name_1)
BEGIN
DECLARE #org_name varchar(256)
SET #org_name = (select Org_Name_1 from organiz)
UPDATE other_table set Org_Name_1 = #org_name
UPDATE the_other_table set Org_name_1 = #org_name
END
Is what I am trying to do possible?
Your current trigger assumes that an update can only ever affect a single row; it also intends to update every single row in the other two tables with an arbitrary value from the source table (not necessarily the row that was updated!). You need to use the inserted pseudo-table to identify the row(s) that fired the trigger and to pull the new value(s) for the Org_Name_1 column. How about this version:
CREATE TRIGGER dbo.updOrgNameTrigger
ON dbo.organiz
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE o SET Org_Name_1 = i.Org_Name_1
FROM dbo.other_table AS o
INNER JOIN inserted AS i
ON o.org_id = i.org_id;
UPDATE o2 SET Org_Name_1 = i.Org_Name_1
FROM dbo.the_other_table AS o2
INNER JOIN inserted AS i
ON o2.org_id = i.org_id;
END
GO