SQL Server - Trigger handling multiple rows in INSERT pseudo table - sql

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?

Related

Insert into 2 tables from a single select query using TSQL

I am trying to insert into 3 tables from one single select statement. Here is what I am trying to do:
insert into dbo.temp1 (name, location, city)
select name, location, city from mytable.
I want to be able to insert into 3 tables once I run the select statement like inserting into temp1, temp2 and temp3.
How can I do this? Thanks.
You can do it maximum for 2 tables with using output:
insert into dbo.temp1 (name, location, city)
output inserted.name, inserted.location, inserted.city into temp2
select name, location, city from mytable
You can't do this in one step*
What you can do is to insert the initial query into a #temp table (or a #table variable) as a staging area, and then insert into the tables from there. Wrap the steps in a transaction to retain ACID:
BEGIN TRAN
select name, location, city
into #TEMP
from mytable;
insert into temp1(name, location, city)
select name, location, city
from #TEMP;
-- Same for temp2 and temp3.
COMMIT TRAN
* Excluding hacks such as a view with an Instead-of Trigger.
The staging table is important from a concurrency point of view, as repeating the original query 3 times may result in different results if there are interim concurrent changes to the source table.
You can.
With a trick.
Create a view, then create an 'instead of' trigger for insert on that view where you insert the stuff into your tables. If you now insert into your view, you finally insert data in 3 tables. Here's a demo
-- 1. create 3 test tables
create table t1( id int, f1 varchar(20))
create table t2( id int, f2 varchar(20))
create table t3( id int, f3 varchar(20))
go
-- 2. create the view
create view Tt as
select t1.ID, t1.f1, t2.f2,t3.f3
from t1
join t2 on t1.ID=t2.ID
join t3 on t1.ID=t3.id
go
-- 3. create the trigger
create trigger Tr_Test on Tt INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
insert into t1 select id,f1 from inserted
insert into t2 select id,f2 from inserted
insert into t3 select id,f3 from inserted
END
GO
-- 4. now do your insert with a single select
insert into tt
select 1,'A','B','C'
-- 5. and watch the 3 tables
select * from t1
select * from t2
select * from t3
voilá, one insert, 3 tables got modified. Wwe don't count the hidden trigger, do we ;-)
There is no way to insert into X tables with one query (Ok it its with insert and output to table).
So you have to write 3 queries.
Or you can generate SQL statments with dynamic queries.
I don't believe you can insert into multiple tables in one statement. You can definitely do it in one transaction, however.
BEGIN TRANSACTION
INSERT INTO dbo.temp1 (name, location, city)
SELECT name, location, city
FROM myTable
INSERT INTO dbo.temp2 (name, location, city)
SELECT name, location, city
FROM myTable2
COMMIT TRANSACTION
You can insert into multiple tables with one select statement using a TRIGGER.
CREATE TRIGGER TEMP2_TEMP3_INSERT ON TEMP1
AFTER INSERT AS
BEGIN
/* create your insert statements for TEMP2 and TEMP3 here
referencing the data from the first insert */
END;
GO
MySQL doesn't support multi-table insertion in a single INSERT statement. Oracle is the only one I'm aware of that does, oddly...
However, you CAN use a transaction and have both of them be contained within one transaction.
MySQL:
START TRANSACTION;
INSERT INTO table1 VALUES ('1','2','3');
INSERT INTO table2 VALUES ('1','2','3');
COMMIT;
SQL Server:
BEGIN TRAN;
INSERT INTO table1 VALUES ('1','2','3');
INSERT INTO table2 VALUES ('1','2','3');
COMMIT;
SQL Server with error catching/rollback:
BEGIN TRANSACTION [Tran1]
BEGIN TRY
INSERT INTO table1 VALUES ('1','2','3')
INSERT INTO table2 VALUES ('1','2','3')
COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Tran1]
END CATCH
GO

Insert trigger checking for specific column

I have an insert trigger on a table. The table has a column source.
The source column could be populated with values b or left empty as in null.
I want the trigger only to fire if the source column is not populated with 'b'. The below trigger codes doesn't give me error, it just never runs.
If I remove the if condition it runs, but I need to check for value of 'b' before it should run.
Create TRIGGER TriggerName
ON tableName
FOR INSERT
AS
BEGIN
DECLARE #source nvarchar(50)
SELECT
#source = (select source from inserted)
if not (#source = 'b')
begin
INSERT INTO ATable(field1, field2, field3)
SELECT
i.field1, i.field2, i.field3
FROM
inserted i
end
end
Your trigger will not handle multiple row operations. You should NOT be using a scalar value inside a trigger. Your entire trigger should be rewritten like this.
INSERT INTO ATable
(
field1
, field2
, field3
)
SELECT i.field1
, i.field2
, i.field3
FROM inserted i
WHERE i.source = 'b'

Adding multiple values to a Table in SQL using a trigger and audit table

The issue I am having is when I insert more than one value into a table or delete a value that exists more than once in a table. I am unsure how to work around this issue.
`CREATE TRIGGER [dbo].[Q5Trigger]
ON [dbo].[WF]
AFTER INSERT, DELETE
AS
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'AuditTable')
BEGIN
CREATE TABLE [dbo].[AuditTable](
Word VARCHAR(100),
Frequency INT,
Date DATETIME,
Type VARCHAR(100)
)
END
IF EXISTS (SELECT * FROM inserted)
BEGIN
INSERT INTO AuditTable VALUES((SELECT Word FROM inserted),(SELECT Frequency FROM inserted), CURRENT_TIMESTAMP, 'Inserted')
END
IF EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable VALUES((SELECT Word FROM deleted),(SELECT Frequency FROM deleted), CURRENT_TIMESTAMP, 'Deleted')
END`
You misunderstand how triggers and INSERTED and DELETED work. When you insert 50 records into a table with a trigger, the trigger gets called once and the INSERTED table has 50 records in it. To do what you are doing, you must insert into the 1 table ALL the records of the other table.
INSERT INTO AuditTable (Word,Frequency,LogWhen,LogType)
SELECT Word,Frequency, CURRENT_TIMESTAMP, 'Inserted' FROM inserted
Your delete would be very similar to this.

sql server - trigger to copy rows from 2 tables

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.

issue with trigger in ms sql server?

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.