Très simplifié, j'ai deux tableaux Source et 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')
Je voudrais déplacer toutes les lignes de @Source
Vers @Target
Et connaître le TargetID
pour chaque SourceID
car il y a aussi les tables SourceChild
et TargetChild
qui doit également être copié et je dois ajouter le nouveau TargetID
dans la colonne FK TargetChild.TargetID
.
Il y a quelques solutions à cela.
scope_identity()
pour remplir le FK de TargetChild
.@Target
Et insérez SourceID
. Vous pouvez ensuite joindre cette colonne pour récupérer le TargetID
pour le FK dans TargetChild
.SET IDENTITY_INSERT OFF
Pour @Target
Et gérez vous-même l'attribution de nouvelles valeurs. Vous obtenez une plage que vous utilisez ensuite dans TargetChild.TargetID
.Je n'aime pas beaucoup d'entre eux. Celui que j'ai utilisé jusqu'à présent est celui des curseurs.
Ce que j'aimerais vraiment faire, c'est utiliser la clause output
de l'instruction insert.
insert into @Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from @Source as S
Mais ce n'est pas possible
The multi-part identifier "S.SourceID" could not be bound.
Mais c'est possible avec une fusion.
merge @Target as T
using @Source as S
on 0=1
when not matched then
insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;
Résultat
TargetID SourceID
----------- -----------
2 1
4 3
Je veux savoir si vous l'avez utilisé? Si vous avez des réflexions sur la solution ou voyez des problèmes avec elle? Cela fonctionne bien dans des scénarios simples, mais peut-être que quelque chose de laid pourrait se produire lorsque le plan de requête devient vraiment compliqué en raison d'une requête source compliquée. Le pire scénario serait que les paires TargetID/SourceID ne correspondent pas.
MSDN a ceci à dire sur le from_table_name
De la clause output .
Est un préfixe de colonne qui spécifie une table incluse dans la clause FROM d'une instruction DELETE, UPDATE ou MERGE utilisée pour spécifier les lignes à mettre à jour ou à supprimer.
Pour une raison quelconque, ils ne disent pas "lignes à insérer, mettre à jour ou supprimer" uniquement "lignes à mettre à jour ou supprimer".
Toutes les pensées sont les bienvenues et des solutions totalement différentes au problème d'origine sont très appréciées.
À mon avis, c'est une excellente utilisation de MERGE et de sortie. J'ai utilisé dans plusieurs scénarios et je n'ai pas rencontré de bizarreries à ce jour. Par exemple, voici la configuration de test qui clone un dossier et tous les fichiers (identité) qu'il contient dans un dossier (guid) nouvellement créé.
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
Je voudrais ajouter un autre exemple à ajouter à l'exemple de @ Nathan, car je l'ai trouvé un peu déroutant.
La mienne utilise des tables réelles pour la plupart, et non des tables temporaires.
J'ai aussi eu mon inspiration ici: n autre exemple
-- 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