Использование merge..output для получения сопоставления между source.id и target.id
Очень упрощен, у меня есть две таблицы 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.
Есть несколько решений для этого.
- Используйте цикл while или курсоры для вставки одной строки (RBAR) в Target за раз и используйте
scope_identity()
для заполнения FK TargetChild
.
- Добавьте временный столбец в
@Target
и вставьте SourceID
. Затем вы можете присоединиться к этому столбцу, чтобы получить TargetID
для FK в TargetChild
.
-
SET IDENTITY_INSERT OFF
для @Target
и обрабатывать присваивание новых значений самостоятельно. Вы получаете диапазон, который затем используется в TargetChild.TargetID
.
Я не очень люблю кого-нибудь из них. Я использовал до сих пор курсоры.
Мне бы очень хотелось использовать предложение output
инструкции insert.
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, который используется для указания строк для обновления или удаления.
По какой-то причине они не говорят "строки для вставки, обновления или удаления только" строк "для обновления или удаления".
Любые мысли приветствуются, и совершенно разные решения исходной проблемы очень ценятся.
Ответы
Ответ 1
По-моему, это большое использование 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
Ответ 2
Я хотел бы добавить еще один пример, чтобы добавить к примеру @Nathan, поскольку я нашел его несколько запутанным.
Шахта использует реальные таблицы по большей части, а не таблицы temp.
Я также получил свое вдохновение отсюда: еще один пример
-- 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