[英]Recursive clone of hierarchy in SQL Server
我在表中有一個層次結構:
Configuration
(
ConfigurationId int identity primary key,
Name nvarchar(100),
Value nvarchar(100),
ParentId` int foreign key referencing ConfigurationId
)
我的任務是克隆具有所有子代的父代,並保持子代的結構。 請記住, ConfigurationId
是身份,它將需要保留身份,不一定必須從1開始。我使用與IsClone
參數用於插入/更新的過程相同的過程。
過程如下所示:
ALTER PROCEDURE [dbo].[Configuration_Save]
@ConfigurationId INT,
@Name NVARCHAR(500),
@Value NVARCHAR(500),
@ParentId INT,
@IsClone BIT
AS
BEGIN
IF @IsClone = 0
BEGIN
IF (@ConfigurationId = 0)
BEGIN
INSERT INTO [Configuration]([Name], [Value], [ParentId])
VALUES (@Name, @Value, @ParentId)
END
ELSE
BEGIN
UPDATE [Configuration]
SET [Name] = @Name,
[Value] = @Value,
ParentId = @ParentId
WHERE ConfigurationId = @ConfigurationId
END
END
ELSE -- IF IsClone = 1
BEGIN
DECLARE @SourceConfigid INT
SET @SourceConfigid = @ConfigurationId
DECLARE @ClonedConfigId INT
INSERT INTO [Configuration] ([Name], [Value], ParentId)
VALUES (@Name, @Value, NULL)
SET @ClonedConfigId = SCOPE_IDENTITY()
-- solution goes here
END
SELECT @ConfigurationId
END
當前數據如下所示:
ConfigurationId Name Value ParentId
-------------------------------------------------------
1 prod NULL NULL
2 Security NULL 1
3 SecurityKey NULL 2
4 Issuer NULL 2
5 Audience NULL 2
6 SyncServer NULL 1
7 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
我希望能夠通過插入新的根配置來克隆根配置(在上一個示例中,其ParentId = NULL
,在ConfigurationId = 1
且Name = prod
),該根配置我通過執行存儲過程輸入名稱,並將行復制到當前位置唯一的區別是作為身份的ConfigurationId
和應在保持層次結構的同時根據新ConfigurationId
更改的ParentId
。
所需數據如下所示:
ConfigurationId Name Value ParentId
------------------------------------------------
1 prod NULL NULL
2 Security NULL 1
3 SecurityKey NULL 2
4 Issuer NULL 2
5 Audience NULL 2
6 SyncServer NULL 1
7 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
21 prod2 NULL NULL
22 Security NULL 21
23 SecurityKey NULL 22
24 Issuer NULL 22
25 Audience NULL 22
26 SyncServer NULL 21
27 Address NULL 26
28 SmtpClient NULL 21
29 Host NULL 28
30 Port NULL 28
31 EnableSsl NULL 28
32 Username NULL 28
33 Password NULL 28
34 FromEmail NULL 28
35 Proxy NULL 21
36 UseProxy NULL 35
37 ProxyAddress NULL 35
38 AddressList NULL 35
39 Report NULL 21
40 ApiUrl NULL 39
與嵌套游標,合並和調用過程/函數相比,我更喜歡CTE解決方案。 我嘗試了幾種以類似名稱列出的解決方案,但沒有成功。
編輯1:示例數據的格式
編輯2:只能克隆根節點,這意味着只有ParentId = NULL的條目才是克隆選項。
任何幫助,將不勝感激。
有許多答案顯示了如何使用附加某些路徑信息的遞歸CTE。 這是一個需要針對您的排序首選項進行調整的示例:
;with cteHierarchy AS (
SELECT ConfigurationId, NAme, Value, ParentId,
CAST(ConfigurationID AS varchar(255)) As HierarchyPath
FROM #Configuration WHERE ParentId IS NULL
UNION ALL
SELECT C.ConfigurationId, C.NAme, C.Value, C.ParentId,
--I prefer CONCAT(), but not sure of your SQL version
CAST(P.HierarchyPath + '.' + CAST(C.ConfigurationID AS varchar(255)) as varchar(255)) As HierarchyPath
FROM #Configuration C
JOIN cteHierarchy P ON C.ParentId = P.ConfigurationId
)
SELECT * FROM cteHierarchy Order By HierarchyPath
以下代碼使用CTE和update
來復制指定層次結構。 CTE從根到葉遞歸地遍歷,並提供一個insert
,該insert
添加行的“副本”。 insert
上的output
子句產生一張修正對表,其中包含每個新行的新舊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;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.