В следующем коде используется CTE и update
для создания копии указанной иерархии.CTE рекурсивно переходит от корня к листьям и передает insert
, который добавляет «копии» строк.Предложение output
в insert
создает таблицу пар исправлений, содержащих старые и новые значения ConfigurationId
для каждой новой строки.Поскольку предложение output
имеет доступ только к вставленным значениям столбца, мы «заимствуем» столбец (Value
) для хранения старых значений ConfigurationId
.Затем update
используется для установки двух столбцов: значения ParentId
обновляются для ссылки на скопированные строки, а значения Value
восстанавливаются из исходных строк.
Обратите внимание, что занятая работа должна бытьзавернутый в транзакцию.Он служит для того, чтобы гарантировать, что копирование завершено, или не осталось никаких отставших, и необходимо, чтобы другие сеансы не видели неполных результатов или не изменяли старые данные Value
, необходимые для завершения копирования.
-- Sample data.
declare @Configuration as Table (
ConfigurationId Int Identity,
Name NVarChar(100),
Value NVarChar(100),
ParentId Int );
insert into @Configuration ( Name, Value, ParentId ) values
( 'prod', NULL, NULL ),
( 'Security', NULL, 1 ),
( 'SecurityKey', NULL, 2 ),
( 'Issuer', NULL, 2 ),
( 'Audience', NULL, 2 ),
( 'SyncServer', NULL, 1 ),
( 'Address', NULL, 6 );
--8 SmtpClient NULL 1
--9 Host NULL 8
--10 Port NULL 8
--11 EnableSsl NULL 8
--12 Username NULL 8
--13 Password NULL 8
--14 FromEmail NULL 8
--15 Proxy NULL 1
--16 UseProxy NULL 15
--17 ProxyAddress NULL 15
--18 AddressList NULL 15
--19 Report NULL 1
--20 ApiUrl NULL 19
-- Raw sample data.
select * from @Configuration;
-- Tree sample data.
with Configuration as (
select ConfigurationId, Name, Value, ParentId,
Cast( Right( '0000' + Cast( ConfigurationId as NVarChar(4) ), 4 ) as NVarChar(1024) ) as Path
from @Configuration
where ParentId is NULL
union all
select CC.ConfigurationId, CC.Name, CC.Value, CC.ParentId,
Cast( Path + N'→' + Right( '0000' + Cast( CC.ConfigurationId as NVarChar(4) ), 4 ) as NVarChar(1024) )
from Configuration as PC inner join
@Configuration as CC on CC.ParentId = PC.ConfigurationId )
select *
from Configuration
order by Path;
-- Copy the tree.
declare @RootConfigurationId as Int = 1;
declare @Fixups as Table ( OriginalConfigurationId NVarChar(10), CopyConfigurationId Int );
-- NB: The isolation level needs to guarantee that the Value in the
-- source rows doesn't get changed whilst we fiddle about, nor do we want anyone else peeking.
begin transaction;
-- Copy the tree and save the new identity values.
-- We cheat and tuck the old ConfigurationId into the Value column so that the
-- output clause can save the original and copy ConfigurationId values for fixup.
with Configuration as (
select ConfigurationId, Name, Value, ParentId
from @Configuration
where ConfigurationId = @RootConfigurationId
union all
select CC.ConfigurationId, CC.Name, CC.Value, CC.ParentId
from Configuration as PC inner join
@Configuration as CC on CC.ParentId = PC.ConfigurationId )
insert into @Configuration ( Name, Value, ParentId )
output inserted.Value, inserted.ConfigurationId into @Fixups
select Name, Cast( ConfigurationId as NVarChar(10) ), ParentId
from Configuration as C;
-- Display the intermediate results.
select * from @Fixups;
select * from @Configuration;
-- Fix up the parentage and replace the original values.
update C
set C.ParentId = F2.CopyConfigurationId, Value = CV.Value
from @Configuration as C inner join -- New rows to be fixed.
@Fixups as F on F.CopyConfigurationId = C.ConfigurationId inner join -- New row identity values.
@Configuration as CV on CV.ConfigurationId = F.OriginalConfigurationId left outer join -- Original Value .
@Fixups as F2 on F2.OriginalConfigurationId = C.ParentId; -- Lookup the new ParentId , if any, for each row.
-- Raw sample data.
select * from @Configuration;
-- Tree sample data.
with Configuration as (
select ConfigurationId, Name, Value, ParentId,
Cast( Right( '0000' + Cast( ConfigurationId as NVarChar(4) ), 4 ) as NVarChar(1024) ) as Path
from @Configuration
where ParentId is NULL
union all
select CC.ConfigurationId, CC.Name, CC.Value, CC.ParentId,
Cast( Path + N'→' + Right( '0000' + Cast( CC.ConfigurationId as NVarChar(4) ), 4 ) as NVarChar(1024) )
from Configuration as PC inner join
@Configuration as CC on CC.ParentId = PC.ConfigurationId )
select *
from Configuration
order by Path;
commit transaction;