How to prevent database trigger updating ##identity after Insert - sql-server

I am adding a trigger to an existing database table, which is used by an application I did not code, and cannot change the code in.
I want to insert some information from TableA into TableB on INSERT into TableA.
Application runs INSERT INTO TableA <-- this updates ##identity
An ON INSERT trigger that runs on TableA then inserts data into TableB <-- this also updates the ##identity with a new value
Application reads ##identity <-- which is from TableB not from TableA as the application is expecting
Is there any way to not update the ##identity from within the trigger?

....since ##identity has no scope you could create your own scope which carries the ##identity value at the end of the trigger
create table tableA(idA int identity(100, 1), colA int)
go
create table tableB(idB int identity(1000, 1), colB int)
go
create trigger triggerA on tableA
for insert
as
begin
if not exists(select * from inserted)
begin
return;
end
declare #tableA##identity int = ##identity;
select ##identity as [##identity_triggerA_in];
--add rows to tableB
insert into tableB(colB)
select object_id
from sys.all_objects
select ##identity as [##identity_after_insert_in_tableB];
if #tableA##identity is not null
begin
declare #sql varchar(100) = concat('create table #t(id int identity(', #tableA##identity, ',1)); insert into #t default values');
exec (#sql);
end
select ##identity as [##identity_triggerA_out];
end
go
insert into tableA(colA) values (10);
select ##identity;
go
insert into tableA(colA)
select top (200) 1
from sys.all_objects;
select ##identity;
go
insert into tableA(colA)
select 1
where 1=2;
select ##identity;
go
drop table tableA;
go
drop table tableB;
go

Related

Trigger to insert multiple record

I'm trying to create a trigger to insert all the value that I delete from a table in a "Backup" table,
Ex:
Table 1: NomePilota, ModelloVettura, NomeScuderia
BackupTable1 (Table 2): NomePilota, ModelloVettura, NomeScuderia
What I want from the trigger to do: Insert into 'Table 2' deleted values from table 1.
I tried like this:
CREATE TRIGGER Backup ON dbo.Table1 AFTER (i can only use after) DELETE AS
BEGIN
DECLARE #Pilota VARCHAR(20) = (SELECT NomePilota FROM deleted)
DECLARE #Vettura VARCHAR(50) = (SELECT ModelloVettura FROM deleted)
DECLARE #Scuderia VARCHAR(20) = (SELECT NomeScuderia FROM deleted)
INSERT INTO Table2 (NomePilota, ModelloVettura, NomeScuderia) VALUES (#Pilota, #Vettura, #Scuderia)
But it send a error:
Cannot insert multiple records in #Pilota, #Vet, #Scud
How can I fix that? Does the deleted table already have a default ID column to use like in a for? Can I use something like vectors? (like #Nome[] = SELECT * FROM Tabella, Insert into Tabella2 (Nome) VALUES #Nome[#Numero (numero is like the record number of nome]).
Why not make life simple?
INSERT INTO Table2 (NomePilota, ModelloVettura, NomeScuderia)
SELECT NomePilota, ModelloVettura, NomeScuderia FROM deleted
The insert statment can work on the results of a select -- here we leverage this to simplify the task at hand.

Trigger is not fired for all rows in case of update(random)

I have created a single trigger to catch all insert, update and delete operations in a replica table but in case of update, two rows are inserted one with before update values and another row with after update values but it is not working as expected.
I have tried all possible ways to do it. Now, I want to know whether I should use transaction at trigger level for update operation or should try something else.
ALTER TRIGGER [dbo].[trgAfterInsertUpdateDelete_xyz] ON [dbo].[xyz]
FOR UPDATE,INSERT, DELETE
AS
declare #accountID int;
declare #billingDate date;
declare #amount decimal(18, 2);
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
values(#accountID,#billingDate,#amount,'INSERT');
END
-- Update Case
IF EXISTS( SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
SELECT d.AccountID, d.BillingDate, d.Amount,
'BeforeUpdate' FROM Inserted i
INNER JOIN Deleted d ON i.ID = d.ID
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount,Audit_Action)
values(#accountID,#billingDate,#amount,'AfterUpdate');
END
-- Delete Case
IF EXISTS( SELECT * FROM deleted) AND NOT EXISTS(SELECT * FROM inserted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
select accountID,billingDate,amount, 'DELETE'
from deleted
END
I expected that if 9 records are updated then there should be before and after update values for all rows but sometimes it skips. As you can see for Inst.8 there should be before and after rows but it has captured only before row and then insert but in actual there was no row deleted and inserted, only updation was done. Sometime out of 9, it pick only before rows not after update rows or sometime 2 or 3 after update rows not before update rows.
your trigger assuming that only one row will be affected for insert / update / delete operatons. Which means the inserted or deleted may contains multiple rows
and the following part does not handle that.
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
Actually you don't required the above at all. You should just insert into the Audit table directly from the inserted or deleted table
For example the "INSERT CASE" should be
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit (AccountID, BillingDate, Amount, Audit_Action)
select AccountID, BillingDate, Amount, Audit_Action= 'INSERT'
from inserted;
END
similarly you need to change accordingly for the Update section. And you are already doing it correctly for the delete section

Update in Merge behaves different? It doesn't get the context_info() while Insert does

I created the following two test tables with a trigger to log all the action (Insert, Delete and Update).
Set up tables and trigger:
-- drop table test; drop table testLog
create table test (id int identity primary key, x int);
create table testLog (idx int identity primary key, Action varchar(10), id int not null,
x_deleted int, x_inserted int, uid uniqueidentifier);
go
-- Trigger to log the changes
create trigger trigger_test on test
after insert, delete, update
as
declare #id uniqueidentifier = context_info();
print #id;
insert testLog (id, Action, x_deleted, x_inserted, uid)
select isnull(d.id, i.id) ,
case when i.id is not null and d.id is not null then 'Updated'
when d.id is not null then 'Deleted'
when i.id is not null then 'Inserted'
end ,
d.x ,
i.x ,
#id
from Deleted d
full outer join inserted i on i.id = d.id;
set context_info 0;
go
Now insert some sample data
set context_info 0
insert test (x) values (10), (20), (30), (40), (50);
SELECT * FROM test;
SELECT * FROM testLog
go
The following statements work fine. The correct context_info() is saved in the log table.
begin tran
declare #newid uniqueidentifier = newid()
--
set context_info #newid
print #newid
insert test(x) values (1)
set context_info #newid
update test set x = 2 where id = 1
SELECT * FROM dbo.testLog;
rollback
go
However, only insert part of the Merge got the value in context_info()?
begin tran
declare #newid uniqueidentifier = newid()
--
set context_info #newid
print #newid;
with v as (select * from (values (1, 11), (2, 22), (6, 66)) v (id, x))
merge test as t using v on t.id = v.id
when matched then update set x = v.x
when not matched by target then insert (x) values (x);
SELECT * FROM dbo.testLog;
rollback
go
The uid of the last two updates got zeros.
Don't set context_info to zero in the trigger. Why would you do that in the first place - it is not the trigger's responsibility to "clean up". The merge statement will cause the trigger to execute for inserts separately from updates. Did you not notice the multiple "prints" in the results pane? That should have been a big clue.

Perform insert for each row taken from a select with SCOPE_IDENTITY()

I need to insert rows in Table1 for each row in Table2.
Also I need to insert for each new inserted row in Table1 a new row in Table3 with inserted record Id.
My script is as follows:
DECLARE #Id INT;
BEGIN TRAN
BEGIN TRY
INSERT INTO dbo.Table1(UN_ID, AT_ID, AA_SUM, AA_START_DATE, AA_END_DATE, WR_ID)
SELECT
116,
3,
100,
'20170501',
'20170601',
WR_ID
FROM Table2
SELECT #Id = SCOPE_IDENTITY()
INSERT INTO dbo.Table3 VALUES (#Id, 10)
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
But this script inserts only one row in Table3.
How can I insert in Table3 for each new inserted row in Table1?
EDIT: Table1 has auto-incremented primary key ID. This ID should be inserted into Table3.
This can be done using output clause as below and before that declare a table variable having the same structure as table 3.
declare #tbl3 table(id int)
INSERT INTO dbo.Table1(UN_ID, AT_ID, AA_SUM, AA_START_DATE, AA_END_DATE, WR_ID)
output Inserted.ID into #tbl3
SELECT
116,
3,
100,
'20170501',
'20170601',
WR_ID
FROM Table2
insert into dbo.table3
select id,10 from #tbl3

Inserting batch of rows into two tables in SQL Server 2008

I have a requirement to insert multiple rows into table1 and at the same time insert a row into table2 with a pkID from table1 and a value that comes from a SP parameter.
I created a stored procedure that performs a batch insert with a table valued parameter which contains the rows to be inserted into table1. But I have a problem with inserting the row into table2 with the corresponding Id (identity) from table1, along with parameter value that I have passed.
Is there anyone who implemented this, or what is the good solution for this?
CREATE PROCEDURE [dbo].[oSP_TV_Insert]
#uID int
,#IsActive int
,#Type int -- i need to insert this in table 2
,#dTableGroup table1 READONLY -- this one is a table valued
AS
DECLARE #SQL varchar(2000)
DECLARE #table1Id int
BEGIN
INSERT INTO dbo.table1
(uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active)
SELECT
#uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active
,#G_Active
FROM #dTableGroup
--the above query will perform batch insert using the records from dTableGroup which is table valued
SET #table1ID = SCOPE_IDENTITY()
-- this below will perform inserting records to table2 with every Id inserted in table1.
Insert into table2(#table1ID , #type)
You need to temporarily store the inserted identity values and then create a second INSERT statement - using the OUTPUT clause.
Something like:
-- declare table variable to hold the ID's that are being inserted
DECLARE #InsertedIDs TABLE (ID INT)
-- insert values into table1 - output the inserted ID's into #InsertedIDs
INSERT INTO dbo.table1(ID, Name, Contact, Address, City, State, Zip, Phone, Active)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT
#ID, Name, Contact, Address, City, State, Zip, Phone, Active, #G_Active
FROM #dTableGroup
and then you can have your second INSERT statement:
INSERT INTO dbo.table2(Table1ID, Type)
SELECT ID, #type FROM #InsertedIDs
See the MSDN docs on the OUTPUT clause for more details on what you can do with the OUTPUT clause - one of the most underused and most "unknown" features of SQL Server these days!
Another approach using OUTPUT clause and only one statement for inserting data in both destination tables:
--Parameters
DECLARE #TableGroup TABLE
(
Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #Type INT;
--End Of parameters
--Destination tables
DECLARE #FirstDestinationTable TABLE
(
FirstDestinationTableID INT IDENTITY(1,1) PRIMARY KEY
,Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #SecondDestinationTable TABLE
(
SecondDestinationTable INT IDENTITY(2,2) PRIMARY KEY
,FirstDestinationTableID INT NOT NULL
,[Type] INT NOT NULL
,CHECK([Type] > 0)
);
--End of destination tables
--Test1
--initialization
INSERT #TableGroup
VALUES ('Bogdan SAHLEAN', '0721200300')
,('Ion Ionescu', '0211002003')
,('Vasile Vasilescu', '0745600800');
SET #Type = 9;
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
SELECT *
FROM #FirstDestinationTable;
SELECT *
FROM #SecondDestinationTable;
--End of test1
--Test2
--initialization
DELETE #TableGroup;
DELETE #FirstDestinationTable;
DELETE #SecondDestinationTable;
INSERT #TableGroup
VALUES ('Ion Ionescu', '0210000000')
,('Vasile Vasilescu', '0745000000');
SET #Type = 0; --Wrong value
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
DECLARE #rc1 INT, #rc2 INT;
SELECT *
FROM #FirstDestinationTable;
SET #rc1 = ##ROWCOUNT;
SELECT *
FROM #SecondDestinationTable;
SET #rc2 = ##ROWCOUNT;
RAISERROR('[Test2 results] #FirstDestinationTable: %d rows; ##SecondDestinationTable: %d rows;',1,1,#rc1,#rc2);
--End of test1
Since you need all inserted identity values, look at the output clause of the insert statement: http://msdn.microsoft.com/en-us/library/ms177564.aspx

Resources