SQL AFTER INSERT,UPDATE Trigger with condition if update specific column - sql-server

I would like to create a trigger that inserts new datasets into the FREE08 table (see example code) either after insert on FREE03. This part works. But i also want to insert new dataset into FREE08 only if specific column (FK2) of FREE03 is updated. Thought this works with the "IF UPDATE(FK2) statement in the trigger.
What happens is i got a new dataset in FREE08 everytime i have any update in FREE03.
How can i get that?
Thanks for your support
USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[Einsatzhistorie Wechsler]
ON [dbo].[FREE03]
AFTER INSERT,UPDATE
AS
SET NOCOUNT ON;
IF UPDATE(FK2)
BEGIN
INSERT INTO FREE08
(
FK1,FK2,TEXT3,DATE1,TEXT2,DATE4
)
Select FK2,FK1,ID,DATE1,TEXT1,DATE11 From inserted
end

I've never found a real use for the UPDATE function, since it tells you whether a column was subject to UPDATE (I.e. appeared in the SET clause), not whether data actually changed. In addition, of course, triggers fire once per statement, not once per row, so your trigger may be dealing with a mixture of rows, some of which had their FK2 changed and some not.
It's usually better to use deleted and compare the previous/current values:
CREATE TRIGGER [dbo].[Einsatzhistorie Wechsler]
ON [dbo].[FREE03]
AFTER INSERT,UPDATE
AS
SET NOCOUNT ON;
INSERT INTO FREE08
(
FK1,FK2,TEXT3,DATE1,TEXT2,DATE4
)
Select i.FK2,i.FK1,i.ID,i.DATE1,i.TEXT1,i.DATE11
From inserted i
left join deleted d
on
i.ID = d.ID and
i.FK2 = d.FK2
where d.ID is null
Here I've assumed a) that ID represents an immutable primary key for the table and b) That FK2 isn't nullable.

Related

SQL Server trigger : when a new row is inserted if column A is blank or null SET value to X

I am trying to create a SQL Server trigger so that when a new row is inserted in CONTACT, if column Contact_Type is blank '' or NULL, it should be set to PCON.
The trigger I have created doesn't work. I have not created any triggers before so I am well out of my depth, any help would be greatly appreciated.
I assume I am messing something up in my where clause in regards to the inserted table?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[EJ_ConTACT_TYPE_DefaultValue_INS]
ON [dbo].[CONTACT]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #defaultvalue varchar(6) = 'PCON'
UPDATE [dbo].[CONTACT]
SET Contact_Type = #defaultvalue
FROM inserted i
WHERE [dbo].[CONTACT].[Contact_Type] = i.Contact_Type
AND (i.[Contact_Type] = '' OR i.[Contact_Type] = NULL)
END
GO
Personally, like I mention in the comments, I would simply create a default constraint:
ALTER TABLE dbo.Contact ADD CONSTRAINT df_Contact_ContactType DEFAULT 'PCON' FOR Contact_Type;
Then you simply omit the column in your INSERT and the column will have the value. For example:
INSERT INTO dbo.Contact (Column1, Column2, Column3)
VALUES('abc',1,'20200916');
This will cause the column Contact_Type to have the value 'PCON'.
As for why your trigger isn't working, one problem is because of your WHERE. [dbo].[CONTACT].[ConTACT_TYPE] = i.ConTACT_TYPE will never be true is the column ConTACT_TYPE (does your column really have that odd casing in it's name..?) if it's value is NULL; NULL is equal to nothing including NULL itself. This also applies to your clause OR [ConTACT_TYPE] = NULL. Finally, I doubt that that value is unique, so it'll update rows it shouldn't.
If you "must" do this in a TRIGGER (I suggest you don't), use the Primary Key for the JOIN. Also, don't use 3+ part naming for columns: 3+ part naming on Columns will be Deprecated. This results in the below:
CREATE TRIGGER [dbo].[EJ_Contact_Type_DefaultValue] ON [dbo].[contact]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
--DECLARE #defaultvalue varchar(6) = 'PCON'
UPDATE C
SET Contact_type = 'PCON'
FROM dbo.Contact C
JOIN inserted i ON C.ID = i.ID
WHERE i.Contact_type = ''
OR i.Contact_type IS NULL;
END;
GO

SQL Server: Capturing All the columns that have changed in a separate table

In my SQl Server I have a table of around 40 attributes/columns. There is a daily load which might update any of these columns. I want to capture the changes in these columns in a separate table with a reason code column telling which column value changed. There might be instances where more than one column value might get changed in a single daily load, in that case the changed log table should capture all these changes separately in rows with each row depicting the individual change.
For Example:
TableA(column1(pk),column2,column3,column4)
values(1,100,ABC,999)
After update:
TableA(column1(pk),column2,column3,column4)
values(1,100,ACD,901)
The corresponding change log table should have two entries:
TabChangeLog(column1,before,after,reason);
values(1,ABC,ACD,'column3 changed')
values(1,999,901,'column4 changed')
I tried implementing this through triggers but am not able to figure out a way to separate each of these changes in separate rows when there are more than one changes. Please help
You need to create a trigger like :
create trigger trigger_name
instead of update as
if update(column1)
begin
insert into TabChangeLog
select inserted.column1, inserted.column3, deleted.column3, 'column3', 'update/change'
from inserted i inner join deleted d
on i.column1 = d.column2
end
if update(column2)
begin
insert into TabChangeLog
select inserted.column1, inserted.column2, deleted.column2, 'column2', 'update/change'
from inserted i inner join deleted d
on i.column1 = d.column2
end
...
https://www.tutorialgateway.org/instead-of-update-triggers-in-sql-server/
Microsoft SQL Server 2016 has a thing called Temporal Tables which would probably simplify your job a lot. It lets you rewind a dataset through time to see the changes:
https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-2017
If you don't want to go that route and use triggers instead. UPDATE triggers have two tables inserted and deleted that let you know what the row state was before and after.
*Edit: These are tables so you have to use SELECT INTO etc to interact with them you can't do conditional logic (if /else)
CREATE TABLE [dbo].[Table1](
[Id] [int] NOT NULL,
[Tail] [int] NOT NULL,
CONSTRAINT [PK_Table1_1] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
)
CREATE TABLE Table1_Audit
(
Audit varchar(100)
)
--drop trigger Table1_OnUPDATE
CREATE TRIGGER Table1_OnUPDATE
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
INSERT INTO Table1_Audit ([Audit])
select CONCAT('Tail changed to' ,inserted.Tail,' for pk Id=',inserted.Id) from inserted inner join
deleted on inserted.Id = deleted.Id --pk must be the same
where
inserted.Tail <> deleted.Tail --field x must be different
END
GO
--truncate table Table1_Audit
--update Table1 set Tail = 5
select * from Table1_Audit

Trigger after update to update linked server table

Basically I have an app where upon column field change (update) another table on a linked server gets updated.
After modifying reference number through an app, this value gets updated in Table_A.
Now I wan't to trigger update of Table_B if Table_A column field reference number is updated.( only check for update of this very field )
USE [SRV1]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[BenRef_UPDATE]
ON [dbo].[Table_A]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
IF UPDATE(refNum)
UPDATE [EXPSRV1].[OPR].[protected].[TransferServiceArrangement]
SET PaymentDetailsBeneficiaryReferenceNumber = i.refNum
FROM inserted i
INNER JOIN deleted d
ON d.party= i.party
END;
Right now I am unable to update Table_A when Trigger is created, no change will occur, when I drop the trigger and modify number the change reflects on the Table_A.
What did I miss ?

Sql CretedDate Trigger

This is the query I used for creating a trigger to update the CreatedDate column of my table "websites"
create trigger [dbo].[trgrforcreateddate] on [dbo].[Websites]
after insert
as
update dbo.websites
set CreatedDate=getdate() from websites w inner join inserted i on w.website=i.website where w.website=i.website
It worked, only one should get updated with Created date (actually, the expected row is updated). But as a result I see
" rows updated"
Why?
For this you should be using a default constraint on CreatedDate instead of a trigger.
alter table dbo.websites add constraint df_websites_CreatedDate default getdate() for CreatedDate;
The trigger is not joining on a unique id, if it was you would see only 1 row affected for each insert. You should also use set nocount on; to prevent extra row result messages from being returned, but in this case it was good that it was not set so that you noticed the error.
alter trigger [dbo].[trgrforcreateddate] on [dbo].[Websites]
after insert
as
begin;
set nocount on;
update w
set w.CreatedDate=getdate()
from dbo.websites w
inner join inserted i
on w.id = i.id;
end;

SQL Server before trigger

I need to control table values uniqueness. It cannot be done by an index or a constraint (error message must show data from another table). I thought of after trigger but since it fires after the insert the below trigger will fire even if values are unique.
--table
CREATE TABLE Names (Id IDENTITY(1,1) NOT NULL, Name VARCHAR(20) NOT NULL)
--first record
INSERT INTO Names VALUES ('John')
--trigger
CREATE TRIGGER [dbo].[Names_Insert_Trigger]
ON [dbo].[Names]
FOR INSERT, UPDATE
AS
SET NOCOUNT ON
IF EXISTS (SELECT Name
FROM inserted
WHERE EXISTS (SELECT * FROM Names N JOIN inserted ON N.Name=inserted.Name))
BEGIN
RAISERROR('This name is already registered in file XYZ.', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
SET NOCOUNT OFF
--I add another record with different value and the trigger fires
INSERT INTO Names VALUES ('Steven')
I also thought of an instead of insert trigger but the actual table has identity set and will likely get new columns in the future which would require updating the trigger code at each change so I can't use the below code:
CREATE TRIGGER [dbo].[Names_Insert_Trigger]
ON [dbo].[Names]
INSTEAD OF INSERT
AS
SET NOCOUNT ON
IF EXISTS (SELECT Name
FROM inserted
WHERE EXISTS (SELECT * FROM Names N JOIN inserted ON N.Name=inserted.Name))
BEGIN
RAISERROR('This name is already registed in file XYZ.', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
ELSE
INSERT INTO Names
SELECT * FROM inserted
SET NOCOUNT OFF
Any ideas how to solve it?
Regards,
Przemek
You can use an after trigger. Just use COUNT instead of EXISTS. You should still have a non-unique index on name to optimize performance and concurrency.
IF (SELECT COUNT(*)
FROM inserted AS i
JOIN dbo.Names AS N ON
N.Name = i.Name
GROUP BY N.Name
) > 1
BEGIN
RAISERROR...
END;
The real solution is to use the UNIQUE constraint to this problem, it's designed to solve it and it's much more performant and safer than a trigger for this usage. The error message is better built client-side and ignore the server genereated one, save for determining the exact reason.
But if you really want to follow the trigger route, use the AFTER version, but fix the query for detect duplicates:
SELECT * FROM (
SELECT Name
FROM Names
WHERE id NOT IN (SELECT id FROM inserted)
) PreviousNames
INNER JOIN inserted ON PreviousNames.Name = inserted.Name
(I'm just showing the query to check duplication that goes into the IF EXIST instruction, not the whole trigger).
It begins by creating a subquery that gets the names NOT being inserted (so that you don't get a false positive), then simply joins again to inserted to check if any value is in both tables.
There is an additional problem that can happen when using SNAPSHOT issolation level. In this mode, the trigger will NOT see the changes made by other connections, nor they'll be blocked until the trigger ends. I'm not quite familiar with the details, but will leave this article as reference and possible solutions: https://sqlserverfast.com/?s=snapshot+integrity

Resources