I have a utility script that is used to insert data into tables in my database. The script has a number of temp table in it that stores the new data to be inserted and a lot of it is related.
So, for example I have tables like so
DECLARE #Table1 TABLE
(
Table1ID INT
Table1Description VARCHAR(50)
Table1Code VARCHAR(5)
)
DECLARE #Table2 TABLE
(
Table2ID INT
Table2Description VARCHAR(50)
Table2Code VARCHAR(5)
)
DECLARE #Relationships TABLE
(
Table1Code VARCHAR(5)
Table2Code VARCHAR(5)
)
So the script populates the data in #Table1 and #Table2, but doesn't populate the ID fields. Once the data has been MERGEd into the database tables, I update the Table1ID and Table2ID fields in a separate statement as they are auto incrementing fields. Then when I use the #Relationships table to populate the database table, I can join to #Table1 and #Table2 to get the actual ID values.
I'm updating the script and I'm wondering if I can MERGE the data from #Table1/#Table2 into the database and update the ID fields in the temp table as part of the MERGE statement using the OUTPUT clause all in one statement?
I think the answer is no as I can't find anything mentioning updating an existing table with the OUTPUT clause, only inserting into a table.
I am still able to do what I need to do, so I'm not after alternatives. I just wondering if it is possible using the OUTPUT Clause
Thanks in advance
Related
I'm writing an import process which will import data from one (somewhat legacy) database to another. The import process takes one flat table with the source data. I have this populating a temp table (#SourcePersonAccount) at the start. The goal is to distribute this data into three destination tables (dbo.Person, dbo.Account & dbo.PersonAccount). This runs within a trigger on a table use SQL Server Replication, so needs to run quickly.
#SourcePersonAccount([AccountNumber], [CompanyId], [TargetPersonId], [TargetAccountId]);
dbo.Person ([Id] pk identity(1,1), [CompanyId], ...);
dbo.Account ([Id] pk identity(1,1), [AccountNumber], ...);
dbo.PersonAccount ([Id], [PersonId] fk_Person_Id, [AccountId] fk_Account_Id);
In my code, I have the TargetPersonId already populated in the #SourcePersonAccount temp table. All that's left is to 1) insert into dbo.Account, 2) update #SourcePersonAccount with the inserted dbo.Account.Id value, 3) insert into dbo.PersonAccount.
One of the challenges is that the AccountNumber and CompanyId make up a composite primary key of the source table, so both are needed to join properly on the #SourcePersonAccount temp table.
I have seen threads addressing similar issues to a certain extent here and here which did not solve my particular problem, mostly due to performance issues.
As stated in this post, the OUTPUT clause cannot output columns that were not included in the insert, so that is not an option here.
One solution I saw that technically can give the desired output (I can't find the link to where I found the suggestion) while using the OUTPUT clause is to actually add and drop a column within the query.
DECLARE #PersonAccountTbl TABLE ([AccountId] INT, [AccountNumber] INT, [CompanyId] INT);
ALTER TABLE [dbo].[Account]
ADD [CompanyId] INT NULL;
INSERT INTO [dbo].[Account]
([AccountNumber], [CompanyId])
OUTPUT INSERTED.[Id], INSERTED.[AccountNumber], INSERTED.[CompanyId]
INTO #PersonAccountTbl
SELECT
[AccountNumber], [CompanyId]
FROM #SourcePersonAccount
WHERE
[TargetAccountId] IS NULL;
ALTER TABLE [dbo].[Account]
DROP COLUMN [CompanyId];
This is not a viable option for my situation.
I tried using MERGE as every thread I've found on this issue recommends using it. I do not like MERGE for a few reasons. I tried it anyways; the below code gives the desired output, but ended up being much too slow for my purposes.
DECLARE #PersonAccountTbl TABLE ([AccountId] INT, [AccountNumber] INT, [CompanyId] INT);
MERGE INTO [dbo].[Account] a
USING #SourcePersonAccount spa
ON spa.[TargetAccountId] IS NULL
WHEN NOT MATCHED THEN
INSERT
([AccountNumber])
VALUES
(spa.[AccountNumber])
OUTPUT INSERTED.[Id], INSERTED.[AccountNumber], spa.[CompanyId]
INTO #PersonAccountTbl ([AccountId], [AccountNumber], [CompanyId]);
UPDATE spa
SET spa.[TargetAccountId] = pat.[AccountId]
FROM #SourcePersonAccount spa
JOIN #PersonAccountTbl pat
ON pat.[AccountNumber] = spa.[AccountNumber]
AND pat.[CompanyId] = spa.[CompanyId];
INSERT INTO [dbo].[PersonAccount]
([PersonId], [AccountId])
SELECT
spa.[TargetPersonId], spa.[TargetAccountId]
FROM #SourcePersonAccount spa
LEFT JOIN [dbo].[PersonAccount] pa
ON pa.[PersonId] = spa.[TargetPersonId]
AND pa.[AccountId] = spa.[TargetAccountId]
WHERE
pa.[Id] IS NULL;
Is there a way other than MERGE or adding/dropping a column to accomplish this?
You can use a SEQUENCE instead of an IDENTITY column. Then you can assign the IDs to a temp table or table variable before you INSERT the data.
While Inserting my temp table data into main table its getting change the order .But if you select the same its showing in proper order .
Note : I have given identity column for temp table and while inserting into main table using the order by column with identity.
Could you please provide the some suggestion to me.
Please find the example as follows.
DECLARE #TempTable as Table(LevelID int identity(1,1) primary key,Header_ID int,typeid int,type varchar(100),name varchar(max),labelvalue varchar(max),
CreatedOn_DT datetime,CreatedBy_ID int,UpdatedOn_DT DATETIME,UpdatedBy_ID int)
Insert Into #TempTable (Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID)
EXECUTE sp_Result_one #id1,#id2,#id3
Go
Insert Into #TempTable (Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID)
EXECUTE sp_Result_Two #id1,#id2,#id3
Go
Insert Into #TempTable (Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID)
EXECUTE sp_Result_Three #id1,#id2,#id3
Go
Insert Into #TempTable (Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID)
EXECUTE sp_Result_Four #id1,#id2,#id3
GO
Insert into ResultTable(Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID)
select Header_ID,typeid,type,labelvalue,name,CreatedDT,CreatedBID,UpdatedDT,UpdatedByID from #TempTable order by LevelID asc
I have one temptable and populating data into it running four stored procedures and finally inserting the data into maintable from temptable.But its inserting the data into irrelevent order.
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.
(forgive me - I'm new to both StackOverflow & SQL)
Tl;dr - When using ##identity (or any other option such as scope_identity or output variable), is it possible to also use a view? Here is an example of a stored procedure using ##identity:
--SNIP--
DECLARE #AID INT
DECLARE #BID INT
INSERT INTO dbo.A (oct1)
VALUES
(#oct1)
SELECT #AID = ##IDENTITY;
INSERT INTO dbo.B (duo1)
VALUES
(#duo2)
SELECT #BID = ##IDENTITY
INSERT INTO dbo.tblAB (AID, BID)
VALUES
(#AID, #BID)
GO
Longer:
When inserting into a table, you can capture the current value of the identity seed using ##identity. This is useful if you want to insert into table A and B, capture the identity value, then insert into table AB relating A to B. Obviously this is for purposes of data normalization.
Let's say you were to abstract the DB Schema with a few that performs inner joins on your tables to make the data easier to work with. How would you populate the cross reference tables properly in that case? Can it be done the same way, if so, how?
Avoid using ##IDENTITY or SCOPE_IDENTITY() if your system is using Parallel plans as there is a nasty bug. Please refer -
http://connect.microsoft.com/SQL/feedback/ViewFeedback.aspx?FeedbackID=328811
Better way to fetch the inserted Identity ID would be to use OUTPUT clause.
CREATE TABLE tblTest
(
Sno INT IDENTITY(1,1) NOT NULL,
FirstName VARCHAR(20)
)
DECLARE #pk TABLE (ID INT)
INSERT INTO tblTest(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT * FROM #pk
EDIT:
It would work with Views as well. Please see the sample below. Hope this is what you were looking for.
CREATE VIEW v1
AS
SELECT sno, firstname FROM tbltest
GO
DECLARE #pk TABLE (ID INT)
INSERT INTO v1(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT ID FROM #pk
##IDENTITY returns the last IDENTITY value produced on a connection, regardless of the table that produced the value, and regardless of the scope of the statement that produced the value.
SCOPE_IDENTITY() returns the last IDENTITY value produced on a connection and by a statement in the same scope, regardless of the table that produced the value. SCOPE_IDENTITY(), like ##IDENTITY, will return the last identity value created in the current session, but it will also limit it to your current scope as well
Although the issue with either of these is fixed by microsoft , I would suggest you should go with "OUTPUT", and yes, it can be used with view as well
I'm inserting a subset of a table into the same table and in order to create records in some mapping tables need to capture both the newly created identity PK, and the matching old PK..
If SQL would support it, something like:
Create table Test (pk identity, description varchar(10))
Declare #PKVALUES TABLE (NewPK int, OLdPk int)
INSERT INTO Test (description)
OUTPUT INSERTED.PK, Test.PK into #PKVALUES
Select description
From Test
Where ...
But, of course, SQL doesn't support Output of values from the FROM table during an INSERT operation..
The only set based alternative I've come across requires locking the whole table while creating the new PKs in a temporary table and then inserting them into the Test table using identity insert.
Is there some way I can accomplish this, (without having to resort to a one record at a time
approach or having to lock the whole table) ?
Thanks,
Ilmar
My preference would be to add a column to store the old pk in and then you can return it from the output clause. However, it is not always possible to change the table.
So, I have a sneaky trick but it involves doing twice as much work on your db. What you do is put the Old PK in the description field in the intial insert. Then you update the description to the value of teh old PK by joining on the description field to the PK.
Create table Test (pk identity, description varchar(10))
Declare #PKVALUES TABLE (NewPK int, OLdPk varchar(10)
INSERT INTO Test (description)
OUTPUT INSERTED.PK, INSERTED.Description into #PKVALUES
SELECT PK from Test where....
UPDATE tnew
SET description = told.description
FROM test told
JOIN test tnew ON CAST(told.PK AS varchar (10)) = t.description