SQL Query to Append Past Order with Most Recent Order - sql-server

I have an order table that has both past membership data and current data. I want to view this data in single row. I have a temp table for past data, but not exactly sure how to write this query to get current data in the same row. I know it has something to do with the MAX(order no). Here is the query to get the past membership data in a temp table
set transaction isolation level read uncommitted
declare
#ship_master_customer_id varchar (10), #cycle_begin_date datetime, #cycle_end_date datetime, #OrderNo varchar(10), #Description Char(100)
create table #t1(ShipMasterCustomerID varchar(10), OrderNo varchar (10), cycle_begin_date datetime, cycle_end_date datetime, Description Char(100))
Insert into #t1
Select SHIP_MASTER_CUSTOMER_ID, ORDER_NO, CYCLE_BEGIN_DATE,CYCLE_END_DATE, DESCRIPTION FROM [ORDER_DETAIL]
where SHIP_MASTER_CUSTOMER_ID = '11115555' and
CYCLE_END_DATE = '2/29/2016'
Select * from #t1
Drop table #t1
Here is my script.
declare
#ship_master_customer_id varchar (10), #cycle_begin_date datetime, #cycle_end_date datetime, #OrderNo varchar(10), #Description Char(100)
create table #t2(ShipMasterCustomerID varchar(10), OrderNo varchar (10), cycle_begin_date datetime, cycle_end_date datetime, Description Char(100))
Insert into #t2 (shipmastercustomerid, orderno, cycle_begin_date, cycle_end_date, DESCRIPTION)
VALUES (1111555,9004731815, 2015/01/01, 2015/31/12,'Annual Mem'),
(1111555, 9005148308, 2016/01/01, 2016/31/12,'Annual Mem'),
(1111222, 9005027152, 2015/01/03, 2016/29/02,'Annual Mem'),
(1111222, 9005440369, 2016/01/03, 2017/31/03,'Annual Mem'),
(2223333, 9005027152, 2014/01/01, 2016/31/12,'Annual Mem'),
(2223333, 9005442116, 2016/01/01, 2017/31/12,'Annual Mem')
Select * from #t2
Drop table #t2
Sample Data

You don't need a temp table. You can query the same table twice, giving it an alias and then use the alias to prefix your column names. Since you didn't give us a complete schema or a fiddle I'm simulating your database with a temp table but the essence is here. There are considerations that you didn't mention, though. Are you guaranteed that every customer will have both a historical AND a current record? If not, they will not appear in the query below because of the INNER JOIN. You could change it to an OUTER join but when customers don't have a new record you will see NULL values in those columns. My point is that here be dragons... this is by no means a complete or bulletproof solution, only a nudge in the right direction.
DECLARE #ORDER_DETAIL AS TABLE(
ShipMasterCustomerId varchar(20),
OrderNo varchar(20),
cycle_begin_date date,
cycle_end_date date,
Description varchar(100)
)
INSERT #ORDER_DETAIL SELECT '11115555', '9005337015', '02/26/15', '2/29/16', 'Membership 26-Feb-2015 to 29-Feb-2016'
INSERT #ORDER_DETAIL SELECT '11115555', '9005743023', '02/28/17', '2/28/17', 'Membership 01-Mar-2016 to 28-Feb-2017'
SELECT
hist.ShipMasterCustomerId,
hist.OrderNo,
hist.cycle_begin_date,
hist.CYCLE_END_DATE,
hist.[Description],
curr.ShipMasterCustomerId,
curr.OrderNo,
curr.cycle_begin_date,
curr.CYCLE_END_DATE,
curr.[Description]
FROM
#ORDER_DETAIL AS hist
INNER JOIN #ORDER_DETAIL AS curr ON (
(curr.ShipMasterCustomerId = hist.ShipMasterCustomerId) AND (curr.cycle_end_date =
(SELECT MAX(cycle_end_date) FROM #ORDER_DETAIL WHERE ShipMasterCustomerId = hist.ShipMasterCustomerId))
)
WHERE
(hist.ShipMasterCustomerId = '11115555')
AND
(hist.cycle_end_date = '2/29/2016')

Related

SQL Server: Fastest way to transform millions of rows of data from one table to multiple others

I have a staging table with more than 6 million rows of flattened data from a CSV file that I bulk inserted.
I need to take each rows, convert various column values from varchar to int/decimal/datetime, and input each row into numerous new database tables all with foreign key relationships, for now I'll simplify this to: (Parent, Child, OptionalChild) .
I don't need to read it row by row, as each single row contains the parent/child/optional child flattened data.
I am currently going through the records row by row using a SELECT TOP 1, then delete each row after its processed but this is taking hours obviously.
Would appreciate some faster / more efficient approaches.
DECLARE #Id UNIQUEIDENTIFIER;
DECLARE #Date DATETIME2;
DECLARE #Code VARCHAR(10);
DECLARE #Number INT;
DECLARE #OptionalChildCode VARCHAR(10);
DECLARE #OptionalChildNumber INT;
WHILE EXISTS(SELECT * FROM Records)
BEGIN
SELECT TOP 1
#Id = Id,
#Date = Date,
#Code = Code,
#Number = Number,
#OptionalChildCode = OptionalChildCode,
#OptionalChildNumber = OptionalChildNumber
FROM Records
DECLARE #OutputTable AS TABLE(Id UNIQUEIDENTIFIER, Name VARCHAR(10))
INSERT INTO (Parent) (ParentDate)
OUTPUT INSERTED.Id, 'ParentId' INTO #OutputTable(Id, Name)
VALUES (CONVERT(DATETIME2,#Date, 20))
INSERT INTO (Child)(ParentId, ChildCode, ChildNumber)
VALUES (
(SELECT ObjectId FROM #OutputTable WHERE Name = 'ParentId'),
#Code,
CONVERT(INT, #Number)
)
IF (#OptionalChildCode IS NULL)
BEGIN
INSERT INTO (Child)(ParentId, ChildCode, ChildNumber)
VALUES (
(SELECT ObjectId FROM #OutputTable WHERE Name = 'ParentId'),
#OptionalChildCode,
CONVERT(INT, #OptionalChildNumber)
)
END
DELETE FROM Records WHERE Id = #Id
END
Records table (all columns from CSV bulk import):
Columns: Id INT, Date VARCHAR(50), Code VARCHAR(50), Number VARCHAR(50), OptionalChildCode VARCHAR(50), OptionalChildNumber VARCHAR(50)
Target tables:
--Parent
Columns: (Id UNIQUEIDENTIFIER, ParentDate DATETIME2)
--Child
Columns: (Id UNIQUEIDENTIFIER, ParentId UNIQUEIDENTIFIER, ChildCode VARCHAR(10), ChildNumber INT)
Sample data (a row from Records table):
1, "2020-01-01-00-00", "Code123", "55", "OptionalCode456", "66"
Expected results:
--Row from Parent table:
111-222-333, 2020-01-01-00-00
--Row from Child table:
333-333-333, 111-222-333, "Code123", 55
--Row from Child table from optional child:
444-444-444, 111-222-333, "OptionalCode456", 66
The issue here is mainly that you need to get the inserted identity numbers matched against the original table, at the same time as inserting multiple child rows. You cannot use OUTPUT in an INSERT to output anything other than inserted columns.
We can start by using a hack involving MERGE to output the other columns.
We can then conditionally unpivot those rows in order to get one or two child rows to insert.
DECLARE #OutputTable AS TABLE(
Id UNIQUEIDENTIFIER,
Code VARCHAR(10),
Number INT,
OptionalChildCode VARCHAR(10),
OptionalChildNumber INT);
MERGE Parent p
USING Records r
ON 1 = 0 -- never match
WHEN NOT MATCHED THEN
INSERT (ParentDate)
VALUES (CONVERT(DATETIME2, r.[Date], 20))
OUTPUT inserted.Id, r.Code, CONVERT(INT, r.Number), OptionalChildCode, CONVERT(INT, r.OptionalChildNumber)
INTO #OutputTable (Id, Code, Number, OptionalChildCode, OptionalChildNumber)
;
INSERT INTO Child
(ParentId, ChildCode, ChildNumber)
SELECT t.Id, v.Code, v.Number
FROM #OutputTable t
CROSS APPLY (
SELECT t.Code, t.Number
UNION ALL
SELECT t.OptionalChildCode, t.OptionalChildNumber
WHERE OptionalChildCode IS NOT NULL
) v;

Inner join in SQL Server based on lookup table

I have the following table structure:
create table table1( ID int,
assettype varchar(50));
create table t1( poolId int,
day_rate float);
create table t2( poolId int,
day_rate float);
create table lookuptable( tablename varchar(50),
assettype varchar(50));
insert into table1 values (1,'abs'), (2,'card');
insert into t1 values ( 1,5), ( 2,10);
insert into t2 values ( 1,15), ( 2,20);
insert into lookuptable values ('t1','abs'), ('t2','card');
SqlFiddle
For a given id based on the assetType field in table1 I need to perform a lookup in the lookup table so that I display if the assettype of the id is abs
poolId day_rate
1 5
2 10
else if the assettype of the id is card
poolId day_rate
1 15
2 20
The reason I have t1 and t2 tables because they have their own set of calculation and based on the asset type of the id i want to use t1 and t2
Would you be able to guide me with some query or steps to go in the right direction
I can think of the case when structure to this but in my case I have 100 entries in the lookuptable and that would mean a case when structure written for 100 times. Is there a better way of handling this?
Try this..
declare #table varchar(20)
select #table=tablename from lookuptable where assettype = 'card'
print #table
declare #query nvarchar(1000)
set #query = N'select * from [' + #table +']';
print #query
EXECUTE sp_executesql #query
Change the assettype in first query..
Are you just trying to get all the rows in table1 and then their day_rates depending on what type of asset they are? Couldn't you do something like this.
select
,table1.ID
,table1.assettype,
,(case when table1.assettype = 'abs' then t1.day_rate else t2.day_rate end) as
day_rate
from table1
inner join t1 on table1.id = t1.poolId
inner join t2 on table1.id = t2.poolId
group by table1.ID, table1.assettype
Let me identify the perspective first.
First, you created this table1 as? well whatever, but this seems a lookup table as well to me.
create table table1(
ID int,
assettype varchar(50)
);
Next, this one is a lookup table as well because this one is your reference for calculation.
create table t1(
poolId int,
day_rate float
);
create table t2(
poolId int,
day_rate float
);
Lastly, is your direct lookup table depending on the assettype defines which table you will get the rate.
create table lookuptable(
tablename varchar(50),
assettype varchar(50)
);
So, i'll try to simplify this.
One is your table1 with an additional column
create table table1(
ID int,
assettype varchar(50),
day_rate float
);
insert into table1 values
(1,'abs',5)
,(2,'card',10)
I have no further reasons yet to split the table as One-To-Many table relationship as I don't see the lookuptable as "child" as well as t1 and t2. You could instead directly change the rates in table1 rate as desired.
Please put some comments so we can arrive at the same page. thanks

SQL Server 2008 R2: Delete duplicate rows from multiple tables and keep original one

I have the 5 employee tables as shown below.
---Table: Emp_1
CREATE TABLE Emp_1
(
Emp_ID VARCHAR(10),
Emp_FName VARCHAR(10),
Emp_LName VARCHAR(10),
Emp_PNumber VARCHAR(10)
);
---Table: Emp_2
CREATE TABLE Emp_2
(
Emp_ID VARCHAR(10),
Emp_FName VARCHAR(10),
Emp_LName VARCHAR(10),
Emp_PNumber VARCHAR(10)
);
---Table: Emp_3
CREATE TABLE Emp_3
(
Emp_ID VARCHAR(10),
Emp_FName VARCHAR(10),
Emp_LName VARCHAR(10),
Emp_PNumber VARCHAR(10)
);
---Table: Emp_4
CREATE TABLE Emp_4
(
Emp_ID VARCHAR(10),
Emp_FName VARCHAR(10),
Emp_LName VARCHAR(10),
Emp_PNumber VARCHAR(10)
);
---Table: Emp_5
CREATE TABLE Emp_5
(
Emp_ID VARCHAR(10),
Emp_FName VARCHAR(10),
Emp_LName VARCHAR(10),
Emp_PNumber VARCHAR(10)
);
--Insertion: Emp_1
INSERT INTO Emp_1 VALUES('A1','Abram','Mak','123');
INSERT INTO Emp_1 VALUES('A2','Sam','William','321');
--Insertion: Emp_2
INSERT INTO Emp_2 VALUES('A3','John','Marsh','456');
INSERT INTO Emp_2 VALUES('A4','Tom','Lee','654');
--Insertion: Emp_3
INSERT INTO Emp_3 VALUES('A5','Abram','Mak','789');
INSERT INTO Emp_3 VALUES('A6','Shawn','Meben','987');
--Insertion: Emp_4
INSERT INTO Emp_4 VALUES('A7','Sam','William','189');
INSERT INTO Emp_4 VALUES('A8','Mark','Boucher','287');
--Insertion: Emp_5
INSERT INTO Emp_5 VALUES('A9','Gery','Joy','907');
INSERT INTO Emp_5 VALUES('A10','Anthony','Desosa','977');
Now I will insert the each table name into Container table.
I have the following table called as Container which contains the table names, which may be many in my
case I have just inserted 5 as shown below.
--Table : Container
CREATE TABLE Container
(
TableName VARCHAR(50)
);
--Insertion
INSERT INTO Container VALUES('Emp_1');
INSERT INTO Container VALUES('Emp_2');
INSERT INTO Container VALUES('Emp_3');
INSERT INTO Container VALUES('Emp_4');
INSERT INTO Container VALUES('Emp_5');
Note: Now I want to delete the duplicate row from each table and want to keep the original as it is.
And the condition for delete the duplicate row is:
If the Emp_FName and Emp_LName is match with the other tables then the duplicated row has to be deleted and
original row keep as it is.
In my example the Emp_FName and Emp_LName : 'Abram','Mak' repeated in the table Emp_3 which has to be deleted and original
one which is present in the table Emp_1 has keep as it is.
And Emp_FName and Emp_LName : 'Sam','William' repeated in the table Emp_4 which has to be deleted and original
one which is present in the table Emp_1 has keep as it is.
For single Table: For single table I can use the following script to delete the duplicate one and keep the original one.
;WITH CTE AS
(
SELECT *,ROW_NUMBER() OVER (PARTITION BY Emp_FName,Emp_LName ORDER BY Emp_FName) Row_Number FROM Emp_1
)
DELETE FROM CTE
WHERE Row_Number > 1;
My question is how to delete duplicate row from the multiple tables which are dynamic I mean in my Container table the tables
may be more than 5 also.
This query will delete it (emp1 > emp2 ... > emp5):
Declare #sql nvarchar(max) = ''
Select #sql = coalesce(#sql, '')+ '
Delete d From ['+c2.TableName+'] as d
Inner join ['+c1.TableName+'] as c on c.Emp_FName = d.Emp_FName and c.Emp_LName = d.Emp_LName;
'
From Container as c1
Inner Join Container as c2 On c2.TableName > c1.TableName
Order By c1.TableName, c2.TableName
Print #sql
EXEC sp_executesql #sql
However, I think you should take some time to think about your system and data model and try to find a better way of doing it without using dynamic queries.

Insert from single table into multiple tables, invalid column name error

I am trying to do the following but getting an "Invalid Column Name {column}" error. Can someone please help me see the error of my ways? We recently split a transaction table into 2 tables, one containing the often updated report column names and the other containing the unchanging transactions. This leave me trying to change what was a simple insert into 1 table to a complex insert into 2 tables with unique columns. I attempted to do that like so:
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.Date, INSERTED.Amount, INSERTED.Id INTO dbo.Transactions
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
The "INSERTED.Date, INSERTED.Amount" are the source of the errors, with or without the "INSERTED." in front.
-----------------UPDATE------------------
Aaron was correct and it was impossible to manage with an insert but I was able to vastly improve the functionality of the insert and add some other business rules with the Merge functionality. My final solution resembles the following:
DECLARE #TransactionsTemp TABLE
(
[Date] DATE NOT NULL,
Amount MONEY NOT NULL,
ReportColumnsId INT NOT NULL
)
MERGE INTO dbo.ReportColumns AS Trgt
USING ( SELECT
{FK}
,[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}) AS Src
ON Src.{FK} = Trgt.{FK}
WHEN MATCHED THEN
UPDATE SET
Trgt.FullName = Src.FullName,
Trgt.Type= Src.Type,
Trgt.Classification = Src.Classification
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
FullName,
Type,
Classification
)
VALUES
(
Src.FullName,
Src.Type,
Src.Classification
)
OUTPUT Src.[Date], Src.Amount, INSERTED.Id INTO #TransactionsTemp;
MERGE INTO dbo.FinancialReport AS Trgt
USING (SELECT
[Date] ,
Amount ,
ReportColumnsId
FROM #TransactionsTemp) AS Src
ON Src.[Date] = Trgt.[Date] AND Src.ReportColumnsId = Trgt.ReportColumnsId
WHEN NOT MATCHED BY TARGET And Src.Amount <> 0 THEN
INSERT
(
[Date],
Amount,
ReportColumnsId
)
VALUES
(
Src.[Date],
Src.Amount,
Src.ReportColumnsId
)
WHEN MATCHED And Src.Amount <> 0 THEN
UPDATE SET Trgt.Amount = Src.Amount
WHEN MATCHED And Src.Amount = 0 THEN
DELETE;
Hope that helps someone else in the future. :)
Output clause will return values you are inserting into a table, you need multiple inserts, you can try something like following
declare #staging table (datecolumn date, amount decimal(18,2),
fullname varchar(50), type varchar(10),
Classification varchar(255));
INSERT INTO #staging
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
Declare #temp table (id int, fullname varchar(50), type varchar(10));
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.id, INSERTED.fullname, INSERTED.type INTO #temp
SELECT
FullName
,Type
,Classification
FROM #stage
INSERT into dbo.transacrions (id, date, amount)
select t.id, s.datecolumn, s.amount from #temp t
inner join #stage s on t.fullname = s.fullname and t.type = s.type
I am fairly certain you will need to have two inserts (or create a view and use an instead of insert trigger). You can only use the OUTPUT clause to send variables or actual inserted values ti another table. You can't use it to split up a select into two destination tables during an insert.
If you provide more information (like how the table has been split up and how the rows are related) we can probably provide a more specific answer.

Using merge..output to get mapping between source.id and target.id

Very simplified, I have two tables Source and Target.
declare #Source table (SourceID int identity(1,2), SourceName varchar(50))
declare #Target table (TargetID int identity(2,2), TargetName varchar(50))
insert into #Source values ('Row 1'), ('Row 2')
I would like to move all rows from #Source to #Target and know the TargetID for each SourceID because there are also the tables SourceChild and TargetChild that needs to be copied as well and I need to add the new TargetID into TargetChild.TargetID FK column.
There are a couple of solutions to this.
Use a while loop or cursors to insert one row (RBAR) to Target at a time and use scope_identity() to fill the FK of TargetChild.
Add a temp column to #Target and insert SourceID. You can then join that column to fetch the TargetID for the FK in TargetChild.
SET IDENTITY_INSERT OFF for #Target and handle assigning new values yourself. You get a range that you then use in TargetChild.TargetID.
I'm not all that fond of any of them. The one I used so far is cursors.
What I would really like to do is to use the output clause of the insert statement.
insert into #Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from #Source as S
But it is not possible
The multi-part identifier "S.SourceID" could not be bound.
But it is possible with a merge.
merge #Target as T
using #Source as S
on 0=1
when not matched then
insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;
Result
TargetID SourceID
----------- -----------
2 1
4 3
I want to know if you have used this? If you have any thoughts about the solution or see any problems with it? It works fine in simple scenarios but perhaps something ugly could happen when the query plan get really complicated due to a complicated source query. Worst scenario would be that the TargetID/SourceID pairs actually isn't a match.
MSDN has this to say about the from_table_name of the output clause.
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.
For some reason they don't say "rows to insert, update or delete" only "rows to update or delete".
Any thoughts are welcome and totally different solutions to the original problem is much appreciated.
In my opinion this is a great use of MERGE and output. I've used in several scenarios and haven't experienced any oddities to date.
For example, here is test setup that clones a Folder and all Files (identity) within it into a newly created Folder (guid).
DECLARE #FolderIndex TABLE (FolderId UNIQUEIDENTIFIER PRIMARY KEY, FolderName varchar(25));
INSERT INTO #FolderIndex
(FolderId, FolderName)
VALUES(newid(), 'OriginalFolder');
DECLARE #FileIndex TABLE (FileId int identity(1,1) PRIMARY KEY, FileName varchar(10));
INSERT INTO #FileIndex
(FileName)
VALUES('test.txt');
DECLARE #FileFolder TABLE (FolderId UNIQUEIDENTIFIER, FileId int, PRIMARY KEY(FolderId, FileId));
INSERT INTO #FileFolder
(FolderId, FileId)
SELECT FolderId,
FileId
FROM #FolderIndex
CROSS JOIN #FileIndex; -- just to illustrate
DECLARE #sFolder TABLE (FromFolderId UNIQUEIDENTIFIER, ToFolderId UNIQUEIDENTIFIER);
DECLARE #sFile TABLE (FromFileId int, ToFileId int);
-- copy Folder Structure
MERGE #FolderIndex fi
USING ( SELECT 1 [Dummy],
FolderId,
FolderName
FROM #FolderIndex [fi]
WHERE FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT
(FolderId, FolderName)
VALUES (newid(), 'copy_'+FolderName)
OUTPUT d.FolderId,
INSERTED.FolderId
INTO #sFolder (FromFolderId, toFolderId);
-- copy File structure
MERGE #FileIndex fi
USING ( SELECT 1 [Dummy],
fi.FileId,
fi.[FileName]
FROM #FileIndex fi
INNER
JOIN #FileFolder fm ON
fi.FileId = fm.FileId
INNER
JOIN #FolderIndex fo ON
fm.FolderId = fo.FolderId
WHERE fo.FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT ([FileName])
VALUES ([FileName])
OUTPUT d.FileId,
INSERTED.FileId
INTO #sFile (FromFileId, toFileId);
-- link new files to Folders
INSERT INTO #FileFolder (FileId, FolderId)
SELECT sfi.toFileId, sfo.toFolderId
FROM #FileFolder fm
INNER
JOIN #sFile sfi ON
fm.FileId = sfi.FromFileId
INNER
JOIN #sFolder sfo ON
fm.FolderId = sfo.FromFolderId
-- return
SELECT *
FROM #FileIndex fi
JOIN #FileFolder ff ON
fi.FileId = ff.FileId
JOIN #FolderIndex fo ON
ff.FolderId = fo.FolderId
I would like to add another example to add to #Nathan's example, as I found it somewhat confusing.
Mine uses real tables for the most part, and not temp tables.
I also got my inspiration from here: another example
-- Copy the FormSectionInstance
DECLARE #FormSectionInstanceTable TABLE(OldFormSectionInstanceId INT, NewFormSectionInstanceId INT)
;MERGE INTO [dbo].[FormSectionInstance]
USING
(
SELECT
fsi.FormSectionInstanceId [OldFormSectionInstanceId]
, #NewFormHeaderId [NewFormHeaderId]
, fsi.FormSectionId
, fsi.IsClone
, #UserId [NewCreatedByUserId]
, GETDATE() NewCreatedDate
, #UserId [NewUpdatedByUserId]
, GETDATE() NewUpdatedDate
FROM [dbo].[FormSectionInstance] fsi
WHERE fsi.[FormHeaderId] = #FormHeaderId
) tblSource ON 1=0 -- use always false condition
WHEN NOT MATCHED
THEN INSERT
( [FormHeaderId], FormSectionId, IsClone, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
VALUES( [NewFormHeaderId], FormSectionId, IsClone, NewCreatedByUserId, NewCreatedDate, NewUpdatedByUserId, NewUpdatedDate)
OUTPUT tblSource.[OldFormSectionInstanceId], INSERTED.FormSectionInstanceId
INTO #FormSectionInstanceTable(OldFormSectionInstanceId, NewFormSectionInstanceId);
-- Copy the FormDetail
INSERT INTO [dbo].[FormDetail]
(FormHeaderId, FormFieldId, FormSectionInstanceId, IsOther, Value, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
SELECT
#NewFormHeaderId, FormFieldId, fsit.NewFormSectionInstanceId, IsOther, Value, #UserId, CreatedDate, #UserId, UpdatedDate
FROM [dbo].[FormDetail] fd
INNER JOIN #FormSectionInstanceTable fsit ON fsit.OldFormSectionInstanceId = fd.FormSectionInstanceId
WHERE [FormHeaderId] = #FormHeaderId
Here's a solution that doesn't use MERGE (which I've had problems with many times I try to avoid if possible). It relies on two memory tables (you could use temp tables if you want) with IDENTITY columns that get matched, and importantly, using ORDER BY when doing the INSERT, and WHERE conditions that match between the two INSERTs... the first one holds the source IDs and the second one holds the target IDs.
-- Setup... We have a table that we need to know the old IDs and new IDs after copying.
-- We want to copy all of DocID=1
DECLARE #newDocID int = 99;
DECLARE #tbl table (RuleID int PRIMARY KEY NOT NULL IDENTITY(1, 1), DocID int, Val varchar(100));
INSERT INTO #tbl (DocID, Val) VALUES (1, 'RuleA-2'), (1, 'RuleA-1'), (2, 'RuleB-1'), (2, 'RuleB-2'), (3, 'RuleC-1'), (1, 'RuleA-3')
-- Create a break in IDENTITY values.. just to simulate more realistic data
INSERT INTO #tbl (Val) VALUES ('DeleteMe'), ('DeleteMe');
DELETE FROM #tbl WHERE Val = 'DeleteMe';
INSERT INTO #tbl (DocID, Val) VALUES (6, 'RuleE'), (7, 'RuleF');
SELECT * FROM #tbl t;
-- Declare TWO temp tables each with an IDENTITY - one will hold the RuleID of the items we are copying, other will hold the RuleID that we create
DECLARE #input table (RID int IDENTITY(1, 1), SourceRuleID int NOT NULL, Val varchar(100));
DECLARE #output table (RID int IDENTITY(1,1), TargetRuleID int NOT NULL, Val varchar(100));
-- Capture the IDs of the rows we will be copying by inserting them into the #input table
-- Important - we must specify the sort order - best thing is to use the IDENTITY of the source table (t.RuleID) that we are copying
INSERT INTO #input (SourceRuleID, Val) SELECT t.RuleID, t.Val FROM #tbl t WHERE t.DocID = 1 ORDER BY t.RuleID;
-- Copy the rows, and use the OUTPUT clause to capture the IDs of the inserted rows.
-- Important - we must use the same WHERE and ORDER BY clauses as above
INSERT INTO #tbl (DocID, Val)
OUTPUT Inserted.RuleID, Inserted.Val INTO #output(TargetRuleID, Val)
SELECT #newDocID, t.Val FROM #tbl t
WHERE t.DocID = 1
ORDER BY t.RuleID;
-- Now #input and #output should have the same # of rows, and the order of both inserts was the same, so the IDENTITY columns (RID) can be matched
-- Use this as the map from old-to-new when you are copying sub-table rows
-- Technically, #input and #output don't even need the 'Val' columns, just RID and RuleID - they were included here to prove that the rules matched
SELECT i.*, o.* FROM #output o
INNER JOIN #input i ON i.RID = o.RID
-- Confirm the matching worked
SELECT * FROM #tbl t

Resources