INSERT Trigger not firing an update? - sql

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

Related

Trigger running every time despite conditional statement

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.

Delete trigger and getting field from another table

I have this delete trigger on an SQL database. The record deletes currently and gets written to an audit table. I have been asked to include in this history table a field from another table that is related to the record being deleted based on SurveyID. I thought I could do something like
select #Status = Status from table where Survey = deleted.Survey
But this is incorrect syntax.
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
Declare #SurveyId int
Declare #StudentUIC varchar(10)
Declare #Status varchar(10)
select #SurveyId = deleted.SurveyID,
#StudentUIC = deleted.StudentUIC
from deleted
select #Status = Status from tbly when SurveyID = deleted.SurveyID
insert into fupSurveyAudit
values(#SurveyId,#StudentUIC,#Status)
End
Arrgh. I think you want this insert in your trigger (and nothing else):
insert into fupSurveyAudit(SurveyId, StudentUIC, status)
select d.SurveyId, d.StudentUIC, y.status
from deleted d left join
tbly y
on d.SurveyId = y.SurveyId;
Notes:
deleted could contain more than one row, so assuming that it has one row can lead to a run-time error or incorrect results.
A left join is needed in case there is no matching row for the status.
You should always include the columns in an insert
Your archive table should have additional columns, such as an identity column and the date of the insert, which are set automatically (and hence not explicitly part of the insert).
Triggers are fired once for each statement (Delete,insert,update) not for each row inside the statement.
You cannot use variables here because when multiple lines are deleted from the table only one line will be inserted in the Audit table because the variable can only hold one value.
You just need a simple insert from the deleted table into the Audit table something like this....
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit(SurveyId, StudentUIC,[Status])
select d.SurveyID
,d.StudentUIC
,y.[Status]
from deleted d
INNER JOIN tbly y ON y.SurveyID = deleted.SurveyID
End
Try this
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit -- Better listed the column list here
select
d.SurveyID, d.StudentUIC, y.Status
from
deleted d JOIN tbly y ON d.SurveyID = y.SurveyID
End

Using IF UPDATE on SQL Trigger when handling multiple inserted/updated records

I use this SQL Server trigger to look for insert/update of multiple records from a specific table and put it into another queue table (for processing later).
ALTER TRIGGER [dbo].[IC_ProductUpdate] ON [dbo].[StockItem]
AFTER INSERT, UPDATE
AS
BEGIN
SELECT RowNum = ROW_NUMBER() OVER(ORDER BY ItemID) , ItemID
INTO #ProductUpdates
FROM INSERTED;
DECLARE #MaxRownum INT;
SET #MaxRownum = (SELECT MAX(RowNum) FROM #ProductUpdates);
DECLARE #Iter INT;
SET #Iter = (SELECT MIN(RowNum) FROM #ProductUpdates);
WHILE #Iter <= #MaxRownum
BEGIN
-- Get Product Id
DECLARE #StockItemID INT = (SELECT ItemID FROM #ProductUpdates WHERE RowNum = #Iter);
-- Proceed If This Product Is Sync-able
IF (dbo.IC_CanSyncProduct(#StockItemID) = 1)
BEGIN
-- Check If There Is A [ProductUpdate] Queue Entry Already Exist For This Product
IF ((SELECT COUNT(*) FROM IC_ProductUpdateQueue WHERE StockItemID = #StockItemID) > 0)
BEGIN
-- Reset [ProductUpdate] Queue Entry
UPDATE IC_ProductUpdateQueue
SET Synced = 0
WHERE StockItemID = #StockItemID
END
ELSE
BEGIN
-- Insert [ProductUpdate] Queue Entry
INSERT INTO IC_ProductUpdateQueue (StockItemID, Synced)
VALUES (#StockItemID, 0)
END
END
SET #Iter = #Iter + 1;
END
DROP TABLE #ProductUpdates;
END
This works fine, however I only want the above trigger to react if certain columns were updated.
The columns I am interested in are:
Name
Description
I know I can use the following T-SQL syntax to check if a column really updated (during update event) like this:
IF (UPDATE(Name) OR UPDATE(Description))
BEGIN
// do something...
END
But, I am not sure how to incorporate this into the above trigger, since my trigger handles multiple rows being updated at same time also.
Any ideas? At which point in the trigger could i use IF (UPDATE(colX))?
First, I would suggest to have one separate trigger for each operation - one for INSERT, and another for UPDATE. Keeps the code cleaner (less messy IF statements and so forth).
The INSERT trigger is pretty simple, since there's nothing to check for updating - and there's absolutely no need for a temporary table and a slow WHILE loop - just two simple, set-based statements and you're done:
CREATE TRIGGER [dbo].[IC_ProductInsert] ON [dbo].[StockItem]
AFTER INSERT
AS
BEGIN
-- update the queue for those entries that already exist
-- those rows that *DO NOT* exist yet are not being touched
UPDATE puq
SET Synced = 0
FROM dbo.IC_ProductUpdateQueue puq
INNER JOIN Inserted i ON puq.StockItemID = i.StockItemID
-- for those rows that don't exist yet - insert the values
INSERT INTO dbo.IC_ProductUpdateQueue (StockItemID, Synced)
SELECT
i.StockItemID, 0
FROM
Inserted i
WHERE
NOT EXISTS (SELECT * FROM dbo.IC_ProductUpdateQueue puq
WHERE puq.StockItemID = i.StockItemID)
END
The UPDATE trigger needs one extra check - to see whether or not one of the two columns of interest has changed. This can be handled quite easily by combining the Inserted pseudo table with the new values (after the UPDATE), and the Deleted pseudo table with the "old" values (before the UPDATE):
ALTER TRIGGER [dbo].[IC_ProductUpdate] ON [dbo].[StockItem]
AFTER UPDATE
AS
BEGIN
-- update the queue for those entries that already exist
-- those rows that *DO NOT* exist yet are not being touched
UPDATE puq
SET Synced = 0
FROM dbo.IC_ProductUpdateQueue puq
INNER JOIN Inserted i ON puq.StockItemID = i.StockItemID
INNER JOIN Deleted d ON d.StockItemID = i.StockItemID
WHERE
i.Name <> d.Name OR i.Description <> d.Description
-- for those rows that don't exist yet - insert the values
INSERT INTO dbo.IC_ProductUpdateQueue (StockItemID, Synced)
SELECT
i.StockItemID, 0
FROM
Inserted i
INNER JOIN
Deleted d ON d.StockItemID = i.StockItemID
WHERE
i.Name <> d.Name OR i.Description <> d.Description
AND NOT EXISTS (SELECT * FROM dbo.IC_ProductUpdateQueue puq
WHERE puq.StockItemID = i.StockItemID)
END
You can join to deleted and use where I.Name <> D.Name...
https://www.mssqltips.com/sqlservertip/2342/understanding-sql-server-inserted-and-deleted-tables-for-dml-triggers/

SQL trigger for update

I was just trying to figure out how to do a basic trigger when I updated a row
Heres the setup
CREATE TABLE marriage(
personid int
married varchar(20)
);
INSERT INTO marriage
values (1, unmarried);
What im trying to do is create a sql trigger that will make it so that when I update a person can only go from married to divorced but not unmarried to divorced.
If anyone can help me with structuring this that would be great
This is what I was looking for if someone was looking for something similar
alter trigger
trigtest3
on married
for update
as
begin
declare #old varchar(20)
declare #new varchar(20)
select #old = married from deleted
select #new = married from inserted
if(#old like 'Unmarried' AND #new like 'Divorced')
rollback
end
SQL Server doesn't provide per-row triggers unfortunately, but only triggers for a complete command. And one single update command can update several rows, so you must look whether at least one affected row has undergone a forbidden change. You do this by joining the deleted and inserted pseudo tables on a column or a combination of columns that uniquely identify a record (i.e. the primary key).
create trigger trg_upd_married on marriage for update as
begin
declare #error_count int
select #error_count = count(*)
from deleted d
join inserted i on i.id = d.id
where d.married = 'Unmarried'
where i.married = 'Divorced'
if #error_count > 0
begin
raiserror('Unmarried persons cannot get divorced.', 16, 121)
rollback transaction
end
end;
The above trigger may still have errors. I am not fluent with TSQL (and just notice that I find its triggers quite clumsy - at least compared to Oracle's triggers I am used to).
You need to use instead of triggers as you need to prevent update. For update triggers are run after the insert happens. Use the following code -
create trigger abc on marriage
for instead of update
as
begin
Begin transaction
if exists(select 1 from deleted as a
inner join inserted as b
on a.personid = b.personid
where a.married = 'unmarried' and b.married = 'Divorced')
begin
raiserror('Status can not be changed from unmarried to Divorced',16,1)
Rollback transaction
end
else
begin
update a
set a.married = b.married
from marriage as a
inner join inserted as b
on a.personid = b.personid
Commit transaction
end
end
Let me know if this helps

SQL Trigger & Inserted Idenities

I've got a problem with a trigger that i can't figure out.
Assume i have two tables, Stu_Table2 & Stu_log. Stu_table2 has some columns, one of which is an automatically generated primary key [stu_id]. The link between the two tables is [stu_name]=[user_id]
The below code works fine for Updates & Deletions (as the primary key already exists). But i'm stuck on the insert - how can i insert the automatically generated primary key from stu_name to log table if it hasn't been generated yet?
Stu_name columns, [stu_id] [Stu_name] [Stu_class]
Stu_log columns, [user_id] [stu_name]
obviously this isn't a real world example, just testing proof of concept.
ALTER TRIGGER [dbo].[stu_testtrigger]
ON [dbo].[Stu_Table2] FOR INSERT, UPDATE, DELETE
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with caller queries SELECT statements.
-- If an update/insert/delete occurs on the main table, the number of records affected
-- should only be based on that table and not what records the triggers may/may not
-- select.
SET NOCOUNT ON;
--
-- Variables Needed for this Trigger
--
DECLARE #stu_ID int
DECLARE #stu_name varchar(15)
DECLARE #stu_class int
--
-- Determine if this is an INSERT,UPDATE, or DELETE Action
--
DECLARE #Action as char(1)
DECLARE #Count as int
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
if #Count > 0
BEGIN
SET #Action = 'D' -- Set Action to 'D'eleted.
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
END
if #Action = 'D'
-- This is a DELETE Record Action
--
BEGIN
SELECT #Stu_id =[stu_id]
,#Stu_name = [stu_name]
FROM DELETED
DELETE [dbo].[stu_log]
WHERE [user_id]=#stu_id
END
Else
BEGIN
--
-- Table INSERTED is common to both the INSERT, UPDATE trigger
--
SELECT #stu_id =[stu_id]
,#stu_name = [stu_name]
FROM INSERTED
if #Action = 'I'
-- This is an Insert Record Action
--
--THIS IS WHERE I'm STUCK i think!!!
BEGIN
INSERT INTO [stu_log]
([user_id]
,[description])
VALUES
(#stu_id
,#stu_name)
END
else
-- This is an Update Record Action
--
BEGIN
UPDATE [stu_log]
SET [user_id] = #stu_id
,[description] = #Stu_name
WHERE [user_id]=#stu_id
END
END
HELP!
Since you seem to want to carry out distinctly different actions for inserts, updates and deletes, I'm not sure why you're cramming all of the actions into a single trigger. I'd just have:
CREATE TRIGGER [dbo].[stu_testtrigger_I]
ON [dbo].[Stu_Table2] AFTER INSERT
AS
INSERT INTO stu_log ([user_id],[description])
SELECT stu_id,stu_name from inserted
GO
CREATE TRIGGER [dbo].[stu_testtrigger_D]
ON [dbo].[Stu_Table2] AFTER DELETE
AS
DELETE FROM stu_log WHERE [user_id] IN (
SELECT stu_id from deleted)
GO
CREATE TRIGGER [dbo].[stu_testtrigger_U]
ON [dbo].[Stu_Table2] AFTER UPDATE
AS
UPDATE l SET user_name = i.user_name
FROM
stu_log l
inner join
inserted i
on l.[user_id] = i.stu_id
GO
Notes:
This works for multi-row inserts, updates and deletes, which your original didn't
I've said AFTER instead of FOR, to make it clearer to you that these actions occur after any activity in Stu_Table2 has already occurred (e.g. the identity value has already been generated, which seems to be your concern).
You should note, however, that AFTER and FOR are synonymous. You'd only get different behaviour if we were doing an INSTEAD OF trigger.
I removed the pointless [user_id] = #stu_id setting from the UPDATE. Given the WHERE clause of this update (or my join equivalent, above), those two must already be equal.