How to better duplicate a set of data in SQL Server

前端 未结 3 653
小蘑菇
小蘑菇 2020-12-21 00:29

I have several related tables that I want to be able to duplicate some of the rows while updating the references.

I want to duplicate a row in Table1, and all of

3条回答
  •  南笙
    南笙 (楼主)
    2020-12-21 00:33

    (ab)use MERGE with OUTPUT clause.

    MERGE can INSERT, UPDATE and DELETE rows. In our case we need only to INSERT. 1=0 is always false, so the NOT MATCHED BY TARGET part is always executed. In general, there could be other branches, see docs. WHEN MATCHED is usually used to UPDATE; WHEN NOT MATCHED BY SOURCE is usually used to DELETE, but we don't need them here.

    This convoluted form of MERGE is equivalent to simple INSERT, but unlike simple INSERT its OUTPUT clause allows to refer to the columns that we need.

    I will write down the definitions of table explicitly. Each primary key in the tables is IDENTITY. I've configured foreign keys as well.

    Baskets

    CREATE TABLE [dbo].[Baskets](
        [BasketId] [int] IDENTITY(1,1) NOT NULL,
        [BasketName] [varchar](50) NOT NULL,
     CONSTRAINT [PK_Baskets] PRIMARY KEY CLUSTERED 
    (
        [BasketId] ASC
    )
    

    Fruits

    CREATE TABLE [dbo].[Fruits](
        [FruitId] [int] IDENTITY(1,1) NOT NULL,
        [BasketId] [int] NOT NULL,
        [FruitName] [varchar](50) NOT NULL,
     CONSTRAINT [PK_Fruits] PRIMARY KEY CLUSTERED 
    (
        [FruitId] ASC
    )
    
    ALTER TABLE [dbo].[Fruits]  WITH CHECK 
    ADD CONSTRAINT [FK_Fruits_Baskets] FOREIGN KEY([BasketId])
    REFERENCES [dbo].[Baskets] ([BasketId])
    
    ALTER TABLE [dbo].[Fruits] CHECK CONSTRAINT [FK_Fruits_Baskets]
    

    Properties

    CREATE TABLE [dbo].[Properties](
        [PropertyId] [int] IDENTITY(1,1) NOT NULL,
        [FruitId] [int] NOT NULL,
        [PropertyText] [varchar](50) NOT NULL,
     CONSTRAINT [PK_Properties] PRIMARY KEY CLUSTERED 
    (
        [PropertyId] ASC
    )
    
    ALTER TABLE [dbo].[Properties]  WITH CHECK 
    ADD CONSTRAINT [FK_Properties_Fruits] FOREIGN KEY([FruitId])
    REFERENCES [dbo].[Fruits] ([FruitId])
    
    ALTER TABLE [dbo].[Properties] CHECK CONSTRAINT [FK_Properties_Fruits]
    

    Copy Basket

    At first copy one row in Baskets table and use SCOPE_IDENTITY to get the generated ID.

    BEGIN TRANSACTION;
    
    -- Parameter of the procedure. What basket to copy.
    DECLARE @VarOldBasketID int = 1;
    
    -- Copy Basket, one row
    DECLARE @VarNewBasketID int;
    
    INSERT INTO [dbo].[Baskets] (BasketName) 
    VALUES ('Friends Basket');
    
    SET @VarNewBasketID = SCOPE_IDENTITY();
    

    Copy Fruits

    Then copy Fruits using MERGE and remember a mapping between old and new IDs in a table variable.

    -- Copy Fruits, multiple rows
    DECLARE @FruitIDs TABLE (OldFruitID int, NewFruitID int);
    
    MERGE INTO [dbo].[Fruits]
    USING
    (
        SELECT
            [FruitId]
            ,[BasketId]
            ,[FruitName]
        FROM [dbo].[Fruits]
        WHERE [BasketId] = @VarOldBasketID
    ) AS Src
    ON 1 = 0
    WHEN NOT MATCHED BY TARGET THEN
    INSERT
        ([BasketId]
        ,[FruitName])
    VALUES
        (@VarNewBasketID
        ,Src.[FruitName])
    OUTPUT Src.[FruitId] AS OldFruitID, inserted.[FruitId] AS NewFruitID
    INTO @FruitIDs(OldFruitID, NewFruitID)
    ;
    

    Copy Properties

    Then copy Properties using remembered mapping between old and new Fruit IDs.

    -- Copy Properties, many rows
    INSERT INTO [dbo].[Properties] ([FruitId], [PropertyText])
    SELECT
        F.NewFruitID
        ,[dbo].[Properties].PropertyText
    FROM
        [dbo].[Properties]
        INNER JOIN @FruitIDs AS F ON F.OldFruitID = [dbo].[Properties].FruitId
    ;
    

    Check results, change rollback to commit once you confirmed that the code works correctly.

    SELECT * FROM [dbo].[Baskets];
    SELECT * FROM [dbo].[Fruits];
    SELECT * FROM [dbo].[Properties];
    
    ROLLBACK TRANSACTION;
    

提交回复
热议问题