I have a trigger that is called when there's an Update, Insert or Delete on a table that is joined to another table by a PK/FK 1 to 1 relationship.
Currently I'm copying rowX from TableA when a U, I or D occurs. I want it to also copy rowX from TableB at the same time.
How to I do this?
USE [Database]
GO
/****** Object: Trigger [dbo].[archiveTable] Script Date: 14/01/2014 3:48:08 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[archiveTable] ON [dbo].[TableA]
AFTER INSERT,UPDATE,DELETE
AS
DECLARE #HistoryType char(1) --"I"=insert, "U"=update, "D"=delete
DECLARE #Id INT
SET #HistoryType=NULL
SET #Id=NULL
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF EXISTS (SELECT * FROM DELETED)
BEGIN
--UPDATE
SET #HistoryType='U'
END
ELSE
BEGIN
--INSERT
SET #HistoryType='I'
END
--handle insert or update data
INSERT INTO [database2].[dbo].[tableA]
(column1, column2, ...)
SET #Id=INSERTED.column5
SELECT
GETDATE(), #HistoryType,
column1, column2, ...
FROM INSERTED
JOIN tableB ON INSERTED.column5 = table5.column1
INSERT INTO [database2].[dbo].[tableB]
(column1, column2, column3, ...)
SELECT
GETDATE(), #HistoryType,
column1, column2, column3, ....
FROM jobdtl WHERE column1 = INSERTED.column5
END
ELSE IF EXISTS(SELECT * FROM DELETED)
BEGIN
--DELETE
SET #HistoryType='D'
same as above except for delete
END
I'm guessing I need an inner join somewhere or do I need a variable to get the #job_id so it knows to copy the relevant info from the second table?
edit - from the looks of it I need to somehow use SCOPE_IDENTITY() however it's not taking the job_id of the transaction it's taking the actual ID of the transaction (ie. 1, 2, 3, etc. whereas I need it to be dynamic as job_id may be 54, 634, 325, etc.)
To run multiple statements in a trigger, enclose the statements in a BEGIN END block.
You may define multiple triggers for a particular event.
To refer to inserted/updated data, use "inserted" pseudo table. For deleted rows, use "deleted" pseudo table.
It's not clear from your question whether you want to insert into TableA/TableB or if these are the tables for which the triggers are defined. (The SELECT statement lacks a from clause)
I think, you can use the UNION clause to insert two rows from different table at one time.
I have made a demo query you just need to change it to your query.
create table #a
(
id int,
name varchar(10)
)
insert into #a
select 1,'ax'
union
select 1,'bx'
select * from #a
drop table #a
This is just the sample query with the logic, and i hope you understand it.
Related
I am using a trigger in SQL Server to capture changes into an audit table. Everything is working fine, except I noticed an instance when the audit table has some wrong data in it (eg. One row containing data from another row). I would guess this occurs <1%. Here is my current code.
CREATE TRIGGER [dbo].[Trigger] ON [dbo].[Table]
FOR INSERT, UPDATE, DELETE AS
BEGIN
SET NOCOUNT ON;
-- Detect inserts ------------------------------------------------------
IF EXISTS (SELECT * FROM inserted) AND NOT EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'I', Column1, Column2, etc.
FROM inserted
RETURN;
END
-- Detect deletes ------------------------------------------------------
IF EXISTS (SELECT * FROM deleted) AND NOT EXISTS (SELECT * FROM inserted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'D', Column1, Column2, etc.
FROM deleted
RETURN;
END
-- Detect updates ------------------------------------------------------
IF EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'U', Column1, Column2, etc.
FROM inserted
RETURN;
END
END;
After doing some research I am thinking this issue occurs because my code was written assuming that the pseudo INSERT table only kept 1 row at a time. This is the new trigger, which works, but I don't know if it will fix my problem. Any thoughts?
CREATE TRIGGER [dbo].[Trigger] ON [dbo].[Table]
FOR INSERT, UPDATE, DELETE AS
BEGIN
SET NOCOUNT ON;
-- Detect inserts ------------------------------------------------------
IF EXISTS (SELECT * FROM inserted) AND NOT EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'I', Column1, Column2, etc.
FROM inserted
RETURN;
END
-- Detect deletes ------------------------------------------------------
IF EXISTS (SELECT * FROM deleted) AND NOT EXISTS (SELECT * FROM inserted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'D', Column1, Column2, etc.
FROM deleted
RETURN;
END
-- Detect updates ------------------------------------------------------
IF EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable (Column1, Column2, etc.)
SELECT 'U', Column1, Column2, etc.
-- new code --
FROM Table T
INNER JOIN inserted i ON i.[Column1] = T.[Column1]
RETURN;
END
END;
UPDATE: I started this thread because I have triggers capturing changes to my tables in their own audit tables, but about 1% of the time, the trigger fails to capture a change. I had thought it was because of there being multiple rows in the pseudo tables, but I now think there's another reason. For whatever reason, my triggers fail to update the audit table or mess up the data somehow.
Should I implement TRY/CATCH?
I have a table table1 with some data:
create table table1
(
c1 varchar(20),
c2 varchar(20)
)
insert into table1 values('1','A')
insert into table1 values('2','B')
insert into table1 values('3','C')
insert into table1 values('4','D')
insert into table1 values('5','E')
insert into table1 values('6','F')
Now I created another table with the same structure called table2 :
create table table2
(
c1 varchar(20),
c2 varchar(20)
)
Then I created an After Insert trigger on table2 :
CREATE TRIGGER trgAfterInsert
ON [dbo].[table2]
FOR INSERT
AS
declare #c1 varchar(20);
declare #c2 varchar(20);
declare #audit_action varchar(100);
select #c1 = i.c1 from inserted i;
select #c2 = i.c2 from inserted i;
set #audit_action = 'Inserted Record -- After Insert Trigger.';
insert into table2_Audit(c1, c2, Audit_Action, Audit_Timestamp)
values(#c1, #c2, #audit_action, getdate());
PRINT 'AFTER INSERT trigger fired.'
GO
There is a problem that when i copy all data of table1 to tabe2 then in Audit table only one record show.It not show all inserted record.
I use this query for copy the record in table2:-
insert into table2(c1,c2) select c1,c2 from table1
Your trigger fires once for each insert statement issued against this table. not one for each row inserted in the table. Hence if more than one row is inserted you trigger definition should be able to handle more than one row.
Instead of using variables to capture values from inserted table and then insert them in table two, simply select from the inserted table and insert the data into Table2_audit.
A trigger to handle this would look something like........
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
insert into table2_Audit (c1,c2,Audit_Action,Audit_Timestamp)
SELECT C1
, C2
, 'Inserted Record -- After Insert Trigger.'
, GETDATE()
FROM inserted ;
PRINT 'AFTER INSERT trigger fired.'
END
GO
Trigger is fired once per entire operation, change your code to:
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
DECLARE #audit_action VARCHAR(100) = 'Inserted Record -- After Insert Trigger.';
INSERT INTO table2_Audit(c1,c2,Audit_Action,Audit_Timestamp)
SELECT i.c1, i.c2, #audit_action, GETDATE()
FROM inserted i;
END
Second don't use PRINT inside trigger;
More info
The behavior you are seeing is by design. DML triggers in SQL Server
are statement level triggers - they are fired once after
INSERT/UPDATE/DELETE/MERGE irrespective of how many rows are affected
by the DML statement. So you should write your logic in the trigger to
handle multiple rows in the inserted/deleted tables
so i have a catalog inside a sql server table, i need to be refreshing every 12 hours this catalog, what's the correct way into doing this?
i have been doing a simple delete and afterwards a bulkcopy of the updated data, but im afraid that another process is using this table and might break something.
Cheers,
You could use MERGE feature. It is available in MS SQL Server 2008+, and also add a try/catch - transaction handling. Try following example:
CREATE TABLE #Temp1
(
Id INT IDENTITY PRIMARY KEY
,Description NVARCHAR(20)
)
CREATE TABLE #Temp2
(
Id INT IDENTITY PRIMARY KEY
,Description NVARCHAR(20)
)
INSERT INTO #Temp1 VALUES ('Example1'), ('Example2'), ('');
INSERT INTO #Temp2 VALUES ('Example1'), ('Example4'), ('Example5');
--First values:
SELECT Id, Description FROM dbo.#Temp1;
SELECT Id, Description FROM dbo.#Temp2;
BEGIN TRY
BEGIN TRANSACTION;
--MERGE statement:
--#Temp1 will be our Target and #Temp2 our Source
MERGE dbo.#Temp1 AS target
USING (SELECT Id, Description FROM #Temp2) AS source
ON (target.Id = source.Id)
--When rows matched and description from Target will be '', then the delete will happen
WHEN MATCHED AND target.Description = ''
THEN DELETE
--When rows matched (by their Id, seen previously), then the update will happen:
WHEN MATCHED
THEN UPDATE SET target.Description = source.Description
OUTPUT $action;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
RAISERROR('<<Transaction uncommited. Rolling back transaction',10,1);
ROLLBACK TRANSACTION;
END CATCH
--Final values:
SELECT Id, Description FROM dbo.#Temp1;
SELECT Id, Description FROM dbo.#Temp2;
DROP TABLE #Temp1;
DROP TABLE #Temp2;
Can anyone point out why this insert trigger is not inserting the new rows into the IVRTestB table?
If Object_ID('MyTrig3', 'TR') IS Not Null
Drop Trigger MyTrig3;
GO
Alter Trigger MyTrig3
On dbo.IVRTest
After Insert, update
AS
Begin
SET NOCOUNT ON;
Insert Into [dbo].[IVRTestB]
(IVRAID, IVRName, DayNumber, OpenFlag)
Select
'i.IVRAID', 'i.IVRName', 'i.DayNumber', 'i.OpenFlag'
From inserted i
INNER JOIN dbo.IVRTestB b
On i.IVRAID = b.IVRAID
END
By putting every column of Inserted into single quotes, you're effectively inserting string literals into your destination table - not the column values!
Use this code instead:
INSERT INTO [dbo].[IVRTestB] (IVRAID, IVRName, DayNumber, OpenFlag)
SELECT
i.IVRAID, i.IVRName, i.DayNumber, i.OpenFlag -- *NO* single quotes here!!!!
FROM
inserted i
-- change this WHERE clause to insert those rows that AREN'T alredy in IVRTestB !
WHERE
i.IVRAID NOT IN (SELECT DISTINCT IVRAID FROM dbo.IVRTestB)
Alter Trigger MyTrig3
On dbo.IVRTest
After Insert
AS
Begin
SET NOCOUNT ON;
IF EXISTS ( SELECT
1
FROM
INSERTED
WHERE
INSERTED.DayNumber IS NULL )
Insert Into
[dbo].[IVRTestB]
(IVRAID,
IVRName,
DayNumber,
OpenFlag)
Select
i.IVRAID,
i.IVRName,
i.DayNumber,
i.OpenFlag
From inserted i
WHERE
INSERTED.DayNumber IS NULL
END
Mohan,
You change nearly worked, I just had to change the not nulls to is not null and the alias for inserted to I
Alter Trigger MyTrig3
On dbo.IVRTest
After Insert
AS
Begin
SET NOCOUNT ON;
IF EXISTS ( SELECT
1
FROM
INSERTED
WHERE
INSERTED.DayNumber IS NOT NULL )
Insert Into
[dbo].[IVRTestB]
(IVRAID,
IVRName,
DayNumber,
OpenFlag)
Select
i.IVRAID,
i.IVRName,
i.DayNumber,
i.OpenFlag
From inserted i
WHERE
i.DayNumber IS NOT NULL
END
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'myTrigger' AND type = 'TR')
BEGIN
DROP TRIGGER myTrigger
END
GO
go
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
declare #seq int
select #seq = seq from inserted
if exists (select * from mytable_backup where seq= #seq) begin
delete from mytable_backup where seq=#seq
end
insert into mytable_backup
select * from inserted
end
go
I've written this trigger to check while inserting if seq column is repeated then update the previous row with same seq if seq doesn't exits insert it with new seq.
In ssis package I'm using OLEDB table(Mytable) as a source which contains.
Name,Age,Seq
Gauraw,30,1
Gauraw,31,1
Kiran,28,3
Kiran,29,3
kiran,28,3
Venkatesh,,4
Venkatesh,28,4
Now I'm loading this table to OLEDB destination(Mytable_backup) as destination.
I suppose to get output as.
Gauraw,31,1
kiran,28,3
Venkatesh,28,4
But I'm getting all the records from Mytable into Mytable_backup.
is anything wrong with my trigger?
I think that this trigger will just take the first row and compare it with the existing. If I understand what you want to do you can quit easy do this:
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'myTrigger' AND type = 'TR')
BEGIN
DROP TRIGGER myTrigger
END
GO
go
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
insert into mytable_backup
select
*
from
inserted
WHERE NOT EXISTS
(
SELECT
NULL
FROM
mytable_backup AS mytable
WHERE
inserted.seq=mytable.seq
)
end
go
EDIT
So I found out what was going on. If you insert all of the rows in one go the inserted contains all the rows.. Sorry my mistake. If there are duplicates in your data your example do not show which to choose. I have chosen the one with the maximum of age (don't know what your requirements is). Here is a update with the full example
Table structure
CREATE TABLE mytable_backup
(
Name VARCHAR(100),
Age INT,
Seq INT
)
GO
Trigger
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY inserted.Seq ORDER BY Age) AS RowNbr,
inserted.*
FROM
inserted
WHERE NOT EXISTS
(
SELECT
NULL
FROM
mytable_backup
WHERE
mytable_backup.Seq=inserted.Seq
)
)
insert into mytable_backup(Age,Name,Seq)
SELECT
CTE.Age,
CTE.Name,
cte.Seq
FROM
CTE
WHERE
CTE.RowNbr=1
end
GO
Insert of test data
INSERT INTO mytable_backup
VALUES
('Gauraw',30,1),
('Gauraw',31,1),
('Kiran',28,3),
('Kiran',29,3),
('kiran',28,3),
('Venkatesh',20,4),
('Venkatesh',28,4)
SELECT * FROM mytable_backup
Drop of the database objects
DROP TRIGGER myTrigger
DROP TABLE mytable_backup
Your original code has two flaws:
It assumes that only one record is inserted at a time.
Your insert into mytable_backup happens outside of the if condition. That insert will execute every time.