In an OUTPUT clause in an INSERT trigger, is it possible to reference both INSERTED tables? - sql-server

On the creation of a new record in a table, I need to create a record in each of two other tables (b and c). The trick is that the two new records need to have the same PK value, which must be a UNIQUEIDENTIFIER and is therefore generated using NEWID() and has no relation to the key of the original record. So, what I want to do in the INSERT TRIGGER looks something like this:
INSERT INTO [b] ([bKey], [Foo])
OUTPUT inserted.[bKey] [cKey], i.[Bar] INTO [c]
SELECT NEWID(), i.[Foo] FROM inserted i
However, this seems to be illegal (In an OUTPUT clause in an INSTEAD OF INSERT trigger, is it possible to reference both INSERTED tables?). Is there any way to do this other than by using a CURSOR and a variable for the result of the NEWID()?

The OUTPUT clause of an INSERT statement cannot reference any tables other than the inserted pseudo table of the output clause - see the notes on from_table_name:
Is a column prefix that specifies a table included in the FROM clause of a DELETE, UPDATE, or MERGE statement that is used to specify the rows to update or delete.

I think you can use a table variable/temporary table to achieve your goal:
DECLARE #tmp TABLE (
[bKey] …,
[Foo] …,
[Bar] …
);
INSERT INTO #tmp ([bKey], [Foo], [Bar])
SELECT NEWID(), [Foo], [Bar] FROM inserted;
INSERT INTO [b] ([bKey], [Foo])
SELECT [bKey], [Foo] FROM #tmp;
INSERT INTO [c] ([cKey], [Bar])
SELECT [bKey], [Bar] FROM #tmp;

Related

Can I grab the inserted IDs when doing multiple inserts?

In my head this sounds improbable, but I'd like to know if I can do it:
INSERT INTO MyTable (Name)
VALUES ('First'),
('Second'),
('Third'),
('Fourth'),
('Fifth');
SELECT INSERTED Name, ID FROM TheAboveQuery
Where ID is an auto-indexed column?
Just to clarify, I want to select ONLY the newly inserted rows.
Starting with SQL Server 2008 you can use OUTPUT clause with INSERT statement
DECLARE #T TABLE (ID INT, Name NVARCHAR(100))
INSERT INTO MyTable (Name)
OUTPUT INSERTED.ID, INSERTED.Name INTO #T
VALUES
('First'),
('Second'),
('Third'),
('Fourth'),
('Fifth');
SELECT Name, ID FROM #T;
UPDATE: if table have no triggers
INSERT INTO MyTable (Name)
OUTPUT INSERTED.ID, INSERTED.Name
VALUES
('First'),
('Second'),
('Third'),
('Fourth'),
('Fifth');
Sure, you can use an IDENTITY property on your ID field, and create the CLUSTERED INDEX on it
ONLINE DEMO
create table MyTable ( ID int identity(1,1),
[Name] varchar(64),
constraint [PK_MyTable] primary key clustered (ID asc) on [Primary]
)
--suppose this data already existed...
INSERT INTO MyTable (Name)
VALUES
('First'),
('Second'),
('Third'),
('Fourth'),
('Fifth');
--now we insert some more... and then only return these rows
INSERT INTO MyTable (Name)
VALUES
('Sixth'),
('Seventh')
select top (##ROWCOUNT)
ID,
Name
from MyTable
order by ID desc
##ROWCOUNT returns the number of rows affected by the last statement executed. You can always see this in the messages tab of SQL Server Management Studio. Thus, we are getting the number of rows inserted and combining it with TOP which limits the rows returned in a query to the specified number of rows (or percentage if you use [PERCENT]). It is important that you use ORDER BY when using TOP otherwise your results aren't guaranteed to be the same
From my previous edited answer...
If you are trying to see what values were inserted, then I assume you are inserting them a different way and this is usually handled with an OUTPUT clause, TRIGGER if you are trying to do something with these records after the insert, etc... more information would be needed.

How to insert rows in another table based on insert in first table

If any insert happens in table A then,i need to insert the last inserted row into table B.
How can I do it by using ##rowcount.
I am trying below code.
create table table1
(
id int identity(1,1),
column1 nvarchar
)
create table table2
(
id int identity(1,1),
column1 nvarchar
)
Create procedure insert1
#column1 nvarchar
AS
Declare #t int,#column2 nvarchar
insert into table1 values(#column1)
select * from table1
set #t= (Select ##IDENTITY from table1)
Insert into table2 values (#t)
Please let me know how can i do the same by trigger.
You could write a trigger something like this:
CREATE TRIGGER trgTableAInsert
ON dbo.Table1
FOR INSERT
AS
INSERT INTO dbo.Table2(Column1)
SELECT Column1
FROM Inserted
Points to note:
a trigger is called once per statement, e.g. if your INSERT statement inserts 10 rows, the trigger is called once and Inserted contains those 10 newly inserted rows (do you want to insert all 10 of those into TableB?)
I would recommend to always use the schema prefix on tables (the dbo. part)
I would recommend to always explicitly specify the list of columns, both on an INSERT as well as a SELECT statement - don't omit those! (or you might run into messy and hard-to-debug issues when suddenly one of the tables changes)
MERGE INTO Table1 AS t1
USING MyTable ON 1=0 -- always generates "not matched by target"
WHEN NOT MATCHED BY TARGET THEN
-- INSERT into Table1:
INSERT (A, B, C) VALUES (t1.A, t1.B, t1.C)
--- .. and INSERT into Table2:
OUTPUT inserted.ID, MyTable.D, MyTable.E, MyTable.F
INTO Table2 (ID, D, E, F);

Retrieve original and new identities mapping from SELECT INSERT statement using OUTPUT clause

I have a table with two columns:
CREATE TABLE MyTable(
Id int IDENTITY(1,1) NOT NULL,
Name nvarchar(100) NOT NULL);
I want to duplicate the data using SELECT INSERT statement:
INSERT INTO MyTable (Name)
SELECT Name FROM MyTable
and here is the trickey part - I want to retrieve a mapping table between the original identity and the new identity:
DECLARE #idsMap TABLE (OriginalId int, NewId int)
I know I suppose to use the OUTPUT clause, but for some reason it doesn't work:
INSERT INTO MyTable (Name)
OUTPUT t.Id, INSERTED.Id INTO #idsMap (OriginalId, NewId)
SELECT Name FROM MyTable t
-- Returns error The multi-part identifier "t.Id" could not be bound.
Related questions:
can SQL insert using select return multiple identities?
Possible to insert with a Table Parameter, and also retrieve identity values?
It can be achieved using MERGE INTO and OUTPUT:
MERGE INTO MyTable AS tgt
USING MyTable AS src ON 1=0 --Never match
WHEN NOT MATCHED THEN
INSERT (Name)
VALUES (src.Name)
OUTPUT
src.Id,
inserted.Id
INTO #idsMap;
How about just adding a new column to MyTable? You can keep it around as long as you need to analysis or whatever. I have to say it seems a bit off to me to create a copy of the table but that is up to you to decide.
Something like this might work for you.
alter table MyTable
add OldID int null;
INSERT INTO MyTable (Name, OldID)
SELECT Name , Id
FROM MyTable t
select * from MyTable

Get values of IDENTITY values when inserting multiple rows into a table having instead of trigger

I'm inserting several rows into a table A which has an instead of insert trigger and an identity column.
Then I need to insert so many rows in a table B where each row hold a reference to the rows inserted in A.
The problem is, I can't use the output clause on A since, the identity will not be set (due to the trigger).
I know for one row I could use ##identity as long as the insertion of rows in A is the last insertion statement inside the trigger, but here I'm talking about multiple rows.
How can I achieve this?
I used code like this ( You CAN use table variable in OUTPUT clause )
declare #T table (requestID int);
INSERT <tableA> (<fields>)
OUTPUT inserted.RequestID into #T
VALUES( <fieldValues1> ),( <fieldValues2> ) ;
Insert <tableB> (ID) select RequestID from #T;

What is the use of OUTPUT clause in sql server

What is the purpose of the OUTPUT clause? I have gone through the MSDN documentation for the OUTPUT clause, which includes the following example:
DELETE FROM dbo.table1
OUTPUT DELETED.* INTO #MyTableVar
WHERE id = 4 OR id = 2;
From the above query, it seems that deleted records are saved in some magic table called deleted, and the query will load those records into table called MyTableVar from the magic deleted table. .
I still do not understand the purpose of the OUTPUT clause usage.
As another SQL example:
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
GO
What is this actually doing? Can anyone explain what this clause is doing with an easy example?
UPDATE with non-functioning example:
create proc test
as
CREATE TABLE dbo.table1
(
id INT,
employee VARCHAR(32)
)
go
INSERT INTO dbo.table1 VALUES
(1, 'Fred')
,(2, 'Tom')
,(3, 'Sally')
,(4, 'Alice')
delete from table1
select * from deleted
This gives me an error when I run it, because it can't see the deleted table.
The general purpose of this clause is to capture the changes made to your data without an additional query, which would introduce locking and blocking issues. Example:
DELETE FROM X WHERE Name = 'Foo'
You want to know which IDs were deleted. You can do this naively like this:
SELECT ID FROM X WHERE Name = 'Foo'
DELETE FROM X WHERE Name = 'Foo'
But these selected IDs are unreliable unless you are running in a transaction with isolation level SERIALIZABLE which is usually not the case. Someone else can add, delete or change "Foo"-Records between your two statements. So instead you can use the OUTPUT clause and get back exactly and reliably the deleted IDs without any performance or reliability issues.
Another frequent use is to get the value of inserted default values, especially when using identity columns. For a single insert you can do this:
CREATE TABLE X
(
ID INT IDENTITY,
Name VARCHAR(10)
);
INSERT X (Name) VALUES ('Foo')
SELECT SCOPE_IDENTITY()
But SCOPE_IDENTITY() can give you only the last inserted ID. If you do multiple inserts, like
INSERT X (Name) VALUES ('Foo'), ('Bar')
or
INSERT X (Name) SELECT OtherName FROM Y
and you want to know the inserted IDs, you are out of luck. You can try to find them with another SELECT, but you need another unique column to even formulate the query and then you run into the same issues as with the DELETE sample above. So, the OUTPUT clause lets you identify neatly which Names got which IDs.
You will need these IDs for example when creating dependent records with foreign keys. Think "Order" and "OrderDetails" which are linked by an OrderID column with an IDENTITY clause. Again, with a single INSERT you can get away with using SCOPE_IDENTITY() or ##IDENTITY, but when inserting multiple orders at once, you will need OUTPUT.
When you perform Insert/Update/Delete operation on particular table and want to know what rows are affected OR want to log them for audit trail OR you want to use multiple values of affected rows in subsequent sql statements, you can use OUTPUT clause.
For Insert statement, it will have INSERTED table.
For Delete statement, it will have DELETED table. In case of Update DELETED table will contain rows (with old values) before update operation performed.
For Update statement, it will have DELETED and INSERTED tables.
DELETED table will contain rows (with old values) before update operation performed.
INSERTED table will contain rows (with new values) after update operation performed.
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
Now your query inserts rows in Production.ScrapReason as well as table variable #MyTableVar. Later it selects inserted rows from Production.ScrapReason and #MyTableVar. Thus you can compare both the resultset and it must have identical rows (considering Production.ScrapReason is empty table.)
I hope it makes sense!
Edit:
Inserted/Deleted tables will be available with Insert/Update/Delete statement and not after that. You may want to store those magic table values in db table or temp table.
Without the OUTPUT clause, how would you know which rows were deleted? Your example seems so simple because you already know the Id values, but what if you did this:
DELETE FROM T WHERE SomeColumn LIKE 'SomePattern%'
And you want to find out what was deleted. That's the purpose of the OUTPUT clause.

Resources