Refracted Paladin
Refracted Paladin

Reputation: 12216

Multiple Table Insert with Merge?

I am trying to insert some rows in a parent/child relationship. How does one accomplish this in SQL?

This would be the base query I'd use to find the info I would be inserting:

SELECT * FROM parentTable p
INNER JOIN childTable c
    ON p.ID = c.ParentTableID
WHERE p.PlanID = 123456

What needs to happen is that I insert the ParentTable row first which is really just a copy of the matched row but with a new PlanID and Created Date. Then I take that ParentTable ID from the inserted row and use that for the same process but for the Child Table.

So I need to do in .Net speak at least is where I loop through all parentTable matches and for each match create 1 childTable row.

I was trying to use MERGE and the OUTPUT clause but it seems like I'm maybe using a square peg for a round hole. Would I be better off using a CURSOR of some sorts?


So this is what I have so far based on the answer below. Unfortunately it isn't grabbing the SCOPE_IDENTITY()...is there a trick to it? Because that is returning NULL I get FK errors on the inserts.

USE MemberCenteredPlan

DECLARE @NewPlanID BIGINT;--49727690
DECLARE @OldPlanID BIGINT;--49725211
DECLARE @NewSWReAssID BIGINT;

SET @NewPlanID = 49727690
SET @OldPlanID = 49725211

BEGIN
INSERT INTO tblSocialWorkerReAssessment 
SELECT 
    @NewPlanID
    ,[Discussed]
    ,Discussion
    ,NULL
    ,NULL
    ,NULL   
    ,CreatedBy
    ,GETDATE()
    ,NULL
    ,NULL
    FROM tblSocialWorkerReAssessment
    WHERE PlanID = @OldPlanID

SELECT @NewSWReAssID = SCOPE_IDENTITY();

INSERT INTO tblPlanDomain
SELECT 
   @NewPlanID
   ,[DomainTypeID]
   ,[MemberOutcomes]
   ,[MemberPreferences]
   ,[DomainDate]
   ,[Steps]
    ,[ClinicalFunctionalConcerns]
    ,[ReportWageES]
    ,[ReportWageSSA]
    ,@NewSWReAssID
    ,[CreatedBy]
   ,GETDATE()
   ,NULL
   ,NULL   
   ,NEWID()
FROM tblPlanDomain 
WHERE planID = @OldPlanID

END

Upvotes: 2

Views: 5772

Answers (2)

anon
anon

Reputation:

You don't need MERGE and you definitely don't need cursors. And an INSERT (or a MERGE) can only ever affect one table at a time, so you'll need to perform multiple statements anyway. If you are only ever dealing with one plan at a time, you can do this:

DECLARE @NewPlanID INT;

INSERT dbo.ParentTable(cols...) 
  SELECT cols...
  FROM dbo.ParentTable WHERE PlanID = 123456;

SELECT @NewPlanID = SCOPE_IDENTITY();

INSERT dbo.ChildTable(ParentTableID, cols...) 
  SELECT @NewPlanID, cols...
  FROM dbo.ChildTable WHERE PlanID = 123456;

If you need to reference multiple new plans, it gets a little more complicated, and in that case you would need to use MERGE (at the present time, INSERT's composable DML is a little on the light side - you can't reference the source table in the OUTPUT clause).

DECLARE @p TABLE(OldPlanID INT, NewPlanID INT);

MERGE dbo.ParentTable WITH (HOLDLOCK)
USING 
(
  SELECT ID, cols... FROM dbo.ParentTable 
  WHERE ID IN (123456, 234567)
) AS src ON src.ID IS NULL
WHEN NOT MATCHED THEN INSERT(cols...)
VALUES(src.cols...)
OUTPUT src.ID, inserted.ID INTO @p;

INSERT dbo.ChildTable(ParentTableID, cols...)
SELECT p.NewPlanID, t.cols... 
FROM dbo.ChildTable AS t
INNER JOIN @p AS p
ON t.ParentTableID = p.OldPlanID;

However, you should be very wary about this... I link to several issues and unresolved bugs with MERGE in this answer over on dba.SE. I've also posted a cautionary tip here and several others agree.

Upvotes: 4

granadaCoder
granadaCoder

Reputation: 27852

I think this is what you're after.

Use Northwind
GO

SET NOCOUNT ON


IF OBJECT_ID('tempdb..#OrderAuditHolder') IS NOT NULL
begin
        drop table #OrderAuditHolder
end


CREATE TABLE #OrderAuditHolder 
( 
    [OriginalOrderID] [int] NOT NULL,
    [NewOrderID] [int] NOT NULL,
    [CustomerID] [nchar](5) NULL,
    [EmployeeID] [int] NULL,
    [OrderDate] [datetime] NULL,
    [RequiredDate] [datetime] NULL,
    [ShippedDate] [datetime] NULL,
    [ShipVia] [int] NULL,
    [Freight] [money] NULL,
    [ShipName] [nvarchar](40) NULL,
    [ShipAddress] [nvarchar](60) NULL,
    [ShipCity] [nvarchar](15) NULL,
    [ShipRegion] [nvarchar](15) NULL,
    [ShipPostalCode] [nvarchar](10) NULL,
    [ShipCountry] [nvarchar](15) NULL,
)


declare @ExampleOrderID int
select @ExampleOrderID = (select top 1 OrderID from dbo.Orders ords where exists (select null from dbo.[Order Details] innerOD where innerOD.OrderID = ords.OrderID ) )

print '/@ExampleOrderID/'
print @ExampleOrderID
print ''


insert into dbo.Orders (CustomerID,EmployeeID,OrderDate,RequiredDate,ShippedDate,ShipVia,Freight,ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry)
output @ExampleOrderID , inserted.OrderID,inserted.CustomerID,inserted.EmployeeID,inserted.OrderDate,inserted.RequiredDate,inserted.ShippedDate,inserted.ShipVia,inserted.Freight,inserted.ShipName,inserted.ShipAddress,inserted.ShipCity,inserted.ShipRegion,inserted.ShipPostalCode,inserted.ShipCountry
into #OrderAuditHolder
Select 
CustomerID,EmployeeID,OrderDate,RequiredDate,ShippedDate,ShipVia,Freight,ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry
from dbo.Orders where OrderID = @ExampleOrderID

print '/#OrderAuditHolder/'
Select * from #OrderAuditHolder
print ''

Insert into dbo.[Order Details] ( OrderID , ProductID , UnitPrice , Quantity , Discount )
Select 
holder.NewOrderID , od.ProductID , od.UnitPrice , od.Quantity , od.Discount
from #OrderAuditHolder holder
join dbo.[Order Details] od on holder.OriginalOrderID = od.OrderID
/* Note, the "join" is on the OriginalOrderID, but the inserted value is the NewOrderID */


/* below is not needed, but shows results */
declare @MaxOrderID int
select @MaxOrderID = (select MAX(OrderID) from dbo.Orders ords where exists (select null from dbo.[Order Details] innerOD where innerOD.OrderID = ords.OrderID ) )
select * from dbo.[Order Details] where OrderID = @MaxOrderID order by OrderID desc
/**/



IF OBJECT_ID('tempdb..#OrderAuditHolder') IS NOT NULL
begin
        drop table #OrderAuditHolder
end



SET NOCOUNT OFF

Upvotes: 0

Related Questions