我们有一个表,该表表示与实体(称为项目)关联的值树,其中ParentID列引用行的父级的id列。id列是一个自动递增的IDENTITY列和主键。根节点的ParentID为0。
我们希望能够克隆给定项目的数据,并使生成的ParentID引用复制值的相应新ID,其方式应满足示例中所述的限制。
例如,在下表中复制ProjectID 611的数据:
id ProjectID Value ParentID -------------------------------------------------- 1 611 Animal 0 2 611 Frog 1 13 611 Cow 1 14 611 Jersey Cow 13 25 611 Plant 0 29 611 Tree 25 31 611 Oak 29
应导致:
id ProjectID Value ParentID -------------------------------------------------- 1 611 Animal 0 2 611 Frog 1 13 611 Cow 1 14 611 Jersey Cow 13 25 611 Plant 0 29 611 Tree 25 31 611 Oak 29 32 612 Animal 0 33 612 Frog 32 34 612 Cow 32 35 612 Jersey Cow 34 36 612 Plant 0 37 612 Tree 36 38 612 Oak 37
限制:
CTE与可以很好地配合使用MERGE,但是在SQL Server 2005中是有问题的。对于先前引起误解的注释,我们深表歉意。
MERGE
下面显示了如何克隆项目(具有多棵树)并修正父项以将新森林与旧森林分开。请注意,它不依赖于ID的任何特定排列,例如,它们不必是密集的,单调递增的…。
-- Sample data. declare @Projects as Table ( Id Int Identity, ProjectId Int, Value VarChar(16), ParentId Int Null ); insert into @Projects ( ProjectId, Value, ParentId ) values ( 611, 'Animal', 0 ), ( 611, 'Frog', 1 ), ( 611, 'Cow', 1 ), ( 611, 'Jersey Cow', 3 ), ( 611, 'Plant', 0 ), ( 611, 'Tree', 5 ), ( 611, 'Oak', 6 ); -- Display the raw data. select * from @Projects; -- Display the forest. with IndentedProjects ( Id, ProjectId, Value, ParentId, Level, Path ) as ( -- Start with the top level rows. select Id, ProjectId, Value, ParentId, 0, Convert( VarChar(1024), Right( '000' + Convert( VarChar(4), Id ), 4 ) ) from @Projects where ParentId = 0 union all -- Add the children one level at a time. select P.Id, P.ProjectId, P.Value, P.ParentId, IP.Level + 1, Convert( VarChar(1024), IP.Path + '<' + Right( '000' + Convert( VarChar(4), P.Id ), 4 ) ) from IndentedProjects as IP inner join @Projects as P on P.ParentId = IP.Id ) select Space( Level * 2 ) + Value as [IndentedValue], Id, ProjectId, Value, ParentId, Level, Path from IndentedProjects order by Path; -- Clone the project. declare @OldProjectId as Int = 611; declare @NewProjectId as Int = 42; declare @Fixups as Table ( OldId Int, [NewId] Int ); begin transaction -- With suitable isolation since the hierarchy will be invalid until we apply the fixups! insert into @Projects output Inserted.ParentId, Inserted.Id into @Fixups select @NewProjectId, Value, Id -- Note that we save the old Id in the new ParentId. from @Projects as P where ProjectId = @OldProjectId; -- Apply the fixups. update PNew set ParentId = IsNull( FNew.[NewId], 0 ) -- Output the fixups just to show what is going on. output Deleted.Id, Deleted.ParentId as [ParentIdBeforeFixup], Inserted.ParentId as [ParentIdAfterFixup] from @Fixups as F inner join @Projects as PNew on PNew.Id = F.[NewId] inner join -- Rows we need to fix. @Fixups as FOld on FOld.OldId = PNew.ParentId inner join @Projects as POld on POld.Id = FOld.OldId left outer join @Fixups as FNew on FNew.OldId = POld.ParentId; commit transaction; -- Display the forest. with IndentedProjects ( Id, ProjectId, Value, ParentId, Level, Path ) as ( -- Start with the top level rows. select Id, ProjectId, Value, ParentId, 0, Convert( VarChar(1024), Right( '000' + Convert( VarChar(4), Id ), 4 ) ) from @Projects where ParentId =0 union all -- Add the children one level at a time. select P.Id, P.ProjectId, P.Value, P.ParentId, IP.Level + 1, Convert( VarChar(1024), IP.Path + '<' + Right( '000' + Convert( VarChar(4), P.Id ), 4 ) ) from IndentedProjects as IP inner join @Projects as P on P.ParentId = IP.Id ) select Space( Level * 2 ) + Value as [IndentedValue], Id, ProjectId, Value, ParentId, Level, Path from IndentedProjects order by Path;