Использование merge..output для отображения между source.id и target.id - PullRequest
59 голосов
/ 20 марта 2011

Очень упрощенно, у меня есть две таблицы Source и 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')

Я хотел бы переместить все строки с @Source на @Target и знать TargetID для каждого SourceID, потому чтоесть также таблицы SourceChild и TargetChild, которые также необходимо скопировать, и мне нужно добавить новый TargetID в столбец TargetChild.TargetID FK.

Для этого есть несколько решений.

  1. Используйте цикл или курсоры while для вставки одной строки (RBAR) в Target за раз и используйте scope_identity() для заполненияФК TargetChild.
  2. Добавьте временный столбец к @Target и вставьте SourceID.Затем вы можете присоединиться к этому столбцу, чтобы получить TargetID для FK в TargetChild.
  3. SET IDENTITY_INSERT OFF для @Target и обрабатывать назначение новых значений самостоятельно.Вы получаете диапазон, который затем используете в TargetChild.TargetID.

. Я не очень люблю их.Я использовал до сих пор курсоры.

Что я действительно хотел бы сделать, так это использовать выражение output оператора вставки.

insert into @Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from @Source as S

Но это невозможно

The multi-part identifier "S.SourceID" could not be bound.

Но это возможно при слиянии.

merge @Target as T
using @Source as S
on 0=1
when not matched then
  insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;

Результат

TargetID    SourceID
----------- -----------
2           1
4           3

Я хочу знать, использовали ли вы это?Если у вас есть мысли по поводу решения или вы видите какие-либо проблемы с ним?Он отлично работает в простых сценариях, но, возможно, что-то ужасное может произойти, когда план запроса действительно усложняется из-за сложного исходного запроса.В худшем случае пары TargetID / SourceID на самом деле не совпадают.

В MSDN есть это, чтобы сказать о from_table_name предложения output .

Префикс столбца, который указывает таблицу, включенную в предложение FROMоператор DELETE, UPDATE или MERGE, который используется для указания строк, которые нужно обновить или удалить.

По какой-то причине они не говорят «строки для вставки, обновления или удаления« только »строк вобновить или удалить ".

Любые мысли приветствуются, и совершенно разные решения оригинальной проблемы высоко ценится.

Ответы [ 2 ]

44 голосов
/ 20 марта 2011

На мой взгляд, это отличное использование MERGE и вывода. Я использовал в нескольких сценариях и не испытывал никаких странностей на сегодняшний день. Например, вот тестовая установка, которая клонирует папку и все файлы (идентичность) в ней во вновь созданную папку (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
1 голос
/ 14 сентября 2017

Я хотел бы добавить еще один пример, чтобы добавить его к примеру @Nathan, так как я нахожу это несколько запутанным.

Мой использует в основном реальные таблицы, а не временные таблицы.

Отсюда и мое вдохновение: еще один пример

-- 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...