复制树状结构的一个分支 table
Copying a branch of tree-like structured table
我有以下 table,其中 ID
是 table 的 pk 并且是 IDENTITY
+----+----------+-----------+-------------+
| ID | ParentID | SomeValue | FullPath |
+----+----------+-----------+-------------+
| 1 | NULL | A | (1) |
| 2 | 1 | A.1 | (1)/(2) |
| 3 | 2 | A.1.1 | (1)/(2)/(3) |
| 4 | NULL | B | (4) |
| 5 | 4 | B.1 | (4)/(5) |
| 6 | 4 | B.2 | (4)/(6) |
| 7 | 6 | B.2.1 | (4)/(6)/(7) |
+----+----------+-----------+-------------+
这个table表示存储在hierarchical way中的数据。我正在创建一个将 ID
和 new_ParentID
作为参数作为输入的过程; ID(及其子项和子项的子项等)将是要复制到 new_ParentID
.
的分支
我启动了程序,但我不知道如何获取我创建的父项的新 ID 以便添加它的子项。例如,如果我想将A.1(和A.1.1)复制到B.2中,一旦A.1-Copied被创建,我不知道它的ID
把它写成ParentID
的 A.1.1-已复制。我知道函数 SCOPE_IDENTITY
,但我不知道如何在 CTE
中使用它。这是我目前拥有的:
;WITH Branch
AS
(
SELECT ID,
ParentGroupID,
SomeValue
FROM
#Table1 A
WHERE
ID = @ID
UNION ALL
SELECT E.ID,
E.ParentGroupID,
E.SomeValue
FROM
#Table1 E
INNER JOIN Branch T
ON T.ID = E.ParentGroupID
)
INSERT INTO #Table1
SELECT
CASE WHEN ParentGroupID IS NULL
THEN @new_ParentID
ELSE ???,
SomeValue + '-Copied'
FROM
Branch
如何设法使用 SCOPE_IDENTITY
正确设置我复制的分支的子项的新父项?
编辑:
假设我想复制 ID 为 4 的分支(所以整个 B 分支)到 ID 2(所以 A.1 分支),我们应该有如下数据:
+----+----------+------------+-----------------------+
| ID | ParentID | SomeValue | FullPath |
+----+----------+------------+-----------------------+
| 1 | NULL | A | (1) |
| 2 | 1 | A.1 | (1)/(2) |
| 3 | 2 | A.1.1 | (1)/(2)/(3) |
| 4 | NULL | B | (4) |
| 5 | 4 | B.1 | (4)/(5) |
| 6 | 4 | B.2 | (4)/(6) |
| 7 | 6 | B.2.1 | (4)/(6)/(7) |
| 8 | 2 | B-Copy | (1)/(2)/(8) |
| 9 | 8 | B.1-Copy | (1)/(2)/(8)/(9) |
| 10 | 8 | B.2-Copy | (1)/(2)/(8)/(10) |
| 11 | 10 | B.2.1-Copy | (1)/(2)/(8)/(10)/(11) |
+----+----------+------------+-----------------------+
我有程序可以在之后更新 SomeValue
和 FullPath
值,所以不用担心这些!我对如何重现层次结构感兴趣
下面是插入示例数据的代码:
CREATE TABLE #Data
(
ID INT IDENTITY(1,1),
ParentID INT,
SomeValue VARCHAR(30),
FullPath VARCHAR(255)
)
INSERT INTO #Data VALUES(NULL,'A','(1)')
INSERT INTO #Data VALUES('1','A.1','(1)/(2)')
INSERT INTO #Data VALUES('2','A.1.1','(1)/(2)/(3)')
INSERT INTO #Data VALUES(NULL,'B','(4)')
INSERT INTO #Data VALUES('4','B.1','(4)/(5)')
INSERT INTO #Data VALUES('4','B.2','(4)/(6)')
INSERT INTO #Data VALUES('6','B.2.1','(4)/(6)/(7)')
好的,我们不要拐弯抹角,这很乱,需要扫几下。
我们需要先在这里使用一个MERGE
(没有UPDATE
子句),这样我们就可以OUTPUT
新旧ID值到一个table变量中.然后,之后我们需要使用 UPDATE
来更新新路径的所有路径。
您可能 UPDATE
MERGE
中的先前级别,同时 INSERT
MERGE,
中的当前级别 但是,我没有去沿着这条路走下去,因为它可能更混乱。因此,在插入行之后,我使用进一步的 rCTe 来创建新路径和 UPDATE
它们。
这给你下面(注释)SQL:
USE Sandbox;
GO
CREATE TABLE dbo.Data
(
ID INT IDENTITY(1,1),
ParentID INT,
SomeValue VARCHAR(30),
FullPath VARCHAR(255)
)
INSERT INTO dbo.Data
--VALUES has supported multiple rows in 2008, you should be making use of it.
VALUES(NULL,'A','(1)')
,('1','A.1','(1)/(2)')
,('2','A.1.1','(1)/(2)/(3)')
,(NULL,'B','(4)')
,('4','B.1','(4)/(5)')
,('4','B.2','(4)/(6)')
,('6','B.2.1','(4)/(6)/(7)')
GO
--There are your parameters
DECLARE @BranchToCopy int,
@CopysParent int;
SET @BranchToCopy = 4;
SET @CopysParent = 2;
--Table which will have the data to INSERT in
DECLARE @NewData table (ID int,
ParentID int,
SomeValue varchar(30),
FullPath varchar(255),
Level int);
--Will be used in the MERGE's OUTPUT clause to link the new and old IDs
DECLARE @Keys table (OldID int,
NewID int,
Level int);
--Get the hierachical data and INSERT into the @NewData variable
WITH rCTE AS(
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
1 AS Level
FROM dbo.Data D
WHERE ID = @BranchToCopy
UNION ALL
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
r.[Level] + 1
FROM dbo.Data D
JOIN rCTE r ON D.ParentID = r.ID)
INSERT INTO @NewData (ID,ParentID,SomeValue,FullPath,Level)
SELECT r.ID,
r.ParentID,
CONCAT(r.SomeValue,'-Copy'),
r.FullPath,
r.[Level]
FROM rCTE r;
--Uncomment to see results
--SELECT *
--FROM @NewData;
--Yes, we're using a WHILE!
--This, however, is what is known as a "set based loop"
DECLARE @i int = 1;
WHILE @i <= (SELECT MAX(Level) FROM @NewData) BEGIN
--We use MERGE here as it allows us to OUTPUT columns that weren't inserted into the table
MERGE INTO dbo.Data USING (SELECT ND.ID,
CASE ND.ID WHEN @BranchToCopy THEN @CopysParent ELSE K.NewID END AS Parent,
ND.SomeValue,
ND.Level
FROM @NewData ND
LEFT JOIN @Keys K ON ND.ParentID = K.OldID
WHERE ND.Level = @i) U ON 0=1
WHEN NOT MATCHED THEN
INSERT (ParentID, SomeValue)
VALUES (U.Parent, U.SomeValue)
OUTPUT U.ID, inserted.ID, U.Level
INTO @Keys (OldID, NewID, Level);
--Increment
SET @i = @i + 1;
END;
--Uncomment to see results
--SELECT *
--FROM dbo.[Data];
--Now we need to do the FullPath, as that would be a pain to do on the fly
DECLARE @Paths table (ID int, NewPath varchar(255));
--Work out the new paths
WITH rCTE AS(
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
CONVERT(varchar(255),NULL) AS NewPath
FROM dbo.Data D
WHERE D.ID = @CopysParent
UNION ALL
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
CONVERT(varchar(255),CONCAT(ISNULL(r.FullPath,r.NewPath),'/(',D.ID,')'))
FROM dbo.Data D
JOIN rCTE r ON D.ParentID = r.ID
JOIN @Keys K ON D.ID = K.NewID) --As we want only the new rows
INSERT INTO @Paths (ID, NewPath)
SELECT ID, NewPath
FROM rCTe
WHERE FullPath IS NULL;
--Update the table
UPDATE D
SET FullPath = P.NewPath
FROM dbo.Data D
JOIN @Paths P ON D.ID = P.ID;
SELECT *
FROM dbo.Data;
GO
--Clean up
DROP TABLE dbo.Data;
这是一个仅使用 CTE 的解决方案:
路径 config as ( ...
定义了用于计算的 from 和 to id。这一切都可以在 TVF 中完成。
WITH T AS (
select 1 id, null parentid, 'A' somevalue, '(1)' fullpath union all
select 2 id, 1 parentid, 'A.1' somevalue, '(1)/(2)' fullpath union all
select 3 id, 2 parentid, 'A.1.1' somevalue, '(1)/(2)/(3)' fullpath union all
select 4 id, NULL parentid, 'B' somevalue, '(4)' fullpath union all
select 5 id, 4 parentid, 'B.1' somevalue, '(4)/(5)' fullpath union all
select 6 id, 4 parentid, 'B.2' somevalue, '(4)/(6)' fullpath union all
select 7 id, 6 parentid, 'B.2.1' somevalue, '(4)/(6)/(7)' fullpath
)
, config as (
select 4 from_id, 2 to_id
)
, maxid as (
select max(id) maxid from t
)
, initpath as (
select fullpath from t cross join config where id = to_id
)
, subset_from as (
select t.*, maxid + ROW_NUMBER() over (order by id) new_id, ROW_NUMBER() over (order by id) rn from t cross join config cross join maxid where fullpath like '(' + cast(from_id as varchar) + ')%'
)
, subset_count as (
select count(*) subset_count from subset_from
)
, fullpath_replacements (id, parentid, somevalue, new_id, fullpath, new_fullpath, lvl) as (
select id, parentid, somevalue, new_id, fullpath, replace(fullpath, '(' + cast((select sf.id from subset_from sf where rn = 1) as varchar) + ')', '(' + cast((select sf.new_id from subset_from sf where rn = 1) as varchar) + ')'), 1
from subset_from
union all
select id, parentid, somevalue, new_id, fullpath, replace(new_fullpath, '(' + cast((select sf.id from subset_from sf where sf.rn = fr.lvl + 1) as varchar) + ')', '(' + cast((select sf.new_id from subset_from sf where sf.rn = fr.lvl + 1) as varchar) + ')'), fr.lvl + 1
from fullpath_replacements fr where fr.lvl < (select subset_count from subset_count)
)
, final_replacement as (
select id, parentid, somevalue, new_id, fullpath, (select fullpath from t where t.id = (select to_id from config)) + '/' + new_fullpath new_fullpath, isnull((select sf.new_id from subset_from sf where sf.id = fr.parentid), (select to_id from config)) new_parentid
from fullpath_replacements fr where fr.lvl = (select subset_count from subset_count)
)
select id, parentid, somevalue, fullpath
from (
select * from t
union all
select new_id, new_parentid, somevalue, new_fullpath from final_replacement
) t order by id
想法是使用 row_number window 函数创建新的 ID(请参阅 subset_from
部分)。
然后通过 id 在完整路径 id 中进行替换。这是使用递归 CTE fullpath_replacements
来模拟循环来完成的。
这是可行的,因为在完整路径中我总是可以使用括号来标识需要交换完整路径的哪一部分。
这是输出:
我有以下 table,其中 ID
是 table 的 pk 并且是 IDENTITY
+----+----------+-----------+-------------+
| ID | ParentID | SomeValue | FullPath |
+----+----------+-----------+-------------+
| 1 | NULL | A | (1) |
| 2 | 1 | A.1 | (1)/(2) |
| 3 | 2 | A.1.1 | (1)/(2)/(3) |
| 4 | NULL | B | (4) |
| 5 | 4 | B.1 | (4)/(5) |
| 6 | 4 | B.2 | (4)/(6) |
| 7 | 6 | B.2.1 | (4)/(6)/(7) |
+----+----------+-----------+-------------+
这个table表示存储在hierarchical way中的数据。我正在创建一个将 ID
和 new_ParentID
作为参数作为输入的过程; ID(及其子项和子项的子项等)将是要复制到 new_ParentID
.
我启动了程序,但我不知道如何获取我创建的父项的新 ID 以便添加它的子项。例如,如果我想将A.1(和A.1.1)复制到B.2中,一旦A.1-Copied被创建,我不知道它的ID
把它写成ParentID
的 A.1.1-已复制。我知道函数 SCOPE_IDENTITY
,但我不知道如何在 CTE
中使用它。这是我目前拥有的:
;WITH Branch
AS
(
SELECT ID,
ParentGroupID,
SomeValue
FROM
#Table1 A
WHERE
ID = @ID
UNION ALL
SELECT E.ID,
E.ParentGroupID,
E.SomeValue
FROM
#Table1 E
INNER JOIN Branch T
ON T.ID = E.ParentGroupID
)
INSERT INTO #Table1
SELECT
CASE WHEN ParentGroupID IS NULL
THEN @new_ParentID
ELSE ???,
SomeValue + '-Copied'
FROM
Branch
如何设法使用 SCOPE_IDENTITY
正确设置我复制的分支的子项的新父项?
编辑:
假设我想复制 ID 为 4 的分支(所以整个 B 分支)到 ID 2(所以 A.1 分支),我们应该有如下数据:
+----+----------+------------+-----------------------+
| ID | ParentID | SomeValue | FullPath |
+----+----------+------------+-----------------------+
| 1 | NULL | A | (1) |
| 2 | 1 | A.1 | (1)/(2) |
| 3 | 2 | A.1.1 | (1)/(2)/(3) |
| 4 | NULL | B | (4) |
| 5 | 4 | B.1 | (4)/(5) |
| 6 | 4 | B.2 | (4)/(6) |
| 7 | 6 | B.2.1 | (4)/(6)/(7) |
| 8 | 2 | B-Copy | (1)/(2)/(8) |
| 9 | 8 | B.1-Copy | (1)/(2)/(8)/(9) |
| 10 | 8 | B.2-Copy | (1)/(2)/(8)/(10) |
| 11 | 10 | B.2.1-Copy | (1)/(2)/(8)/(10)/(11) |
+----+----------+------------+-----------------------+
我有程序可以在之后更新 SomeValue
和 FullPath
值,所以不用担心这些!我对如何重现层次结构感兴趣
下面是插入示例数据的代码:
CREATE TABLE #Data
(
ID INT IDENTITY(1,1),
ParentID INT,
SomeValue VARCHAR(30),
FullPath VARCHAR(255)
)
INSERT INTO #Data VALUES(NULL,'A','(1)')
INSERT INTO #Data VALUES('1','A.1','(1)/(2)')
INSERT INTO #Data VALUES('2','A.1.1','(1)/(2)/(3)')
INSERT INTO #Data VALUES(NULL,'B','(4)')
INSERT INTO #Data VALUES('4','B.1','(4)/(5)')
INSERT INTO #Data VALUES('4','B.2','(4)/(6)')
INSERT INTO #Data VALUES('6','B.2.1','(4)/(6)/(7)')
好的,我们不要拐弯抹角,这很乱,需要扫几下。
我们需要先在这里使用一个MERGE
(没有UPDATE
子句),这样我们就可以OUTPUT
新旧ID值到一个table变量中.然后,之后我们需要使用 UPDATE
来更新新路径的所有路径。
您可能 UPDATE
MERGE
中的先前级别,同时 INSERT
MERGE,
中的当前级别 但是,我没有去沿着这条路走下去,因为它可能更混乱。因此,在插入行之后,我使用进一步的 rCTe 来创建新路径和 UPDATE
它们。
这给你下面(注释)SQL:
USE Sandbox;
GO
CREATE TABLE dbo.Data
(
ID INT IDENTITY(1,1),
ParentID INT,
SomeValue VARCHAR(30),
FullPath VARCHAR(255)
)
INSERT INTO dbo.Data
--VALUES has supported multiple rows in 2008, you should be making use of it.
VALUES(NULL,'A','(1)')
,('1','A.1','(1)/(2)')
,('2','A.1.1','(1)/(2)/(3)')
,(NULL,'B','(4)')
,('4','B.1','(4)/(5)')
,('4','B.2','(4)/(6)')
,('6','B.2.1','(4)/(6)/(7)')
GO
--There are your parameters
DECLARE @BranchToCopy int,
@CopysParent int;
SET @BranchToCopy = 4;
SET @CopysParent = 2;
--Table which will have the data to INSERT in
DECLARE @NewData table (ID int,
ParentID int,
SomeValue varchar(30),
FullPath varchar(255),
Level int);
--Will be used in the MERGE's OUTPUT clause to link the new and old IDs
DECLARE @Keys table (OldID int,
NewID int,
Level int);
--Get the hierachical data and INSERT into the @NewData variable
WITH rCTE AS(
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
1 AS Level
FROM dbo.Data D
WHERE ID = @BranchToCopy
UNION ALL
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
r.[Level] + 1
FROM dbo.Data D
JOIN rCTE r ON D.ParentID = r.ID)
INSERT INTO @NewData (ID,ParentID,SomeValue,FullPath,Level)
SELECT r.ID,
r.ParentID,
CONCAT(r.SomeValue,'-Copy'),
r.FullPath,
r.[Level]
FROM rCTE r;
--Uncomment to see results
--SELECT *
--FROM @NewData;
--Yes, we're using a WHILE!
--This, however, is what is known as a "set based loop"
DECLARE @i int = 1;
WHILE @i <= (SELECT MAX(Level) FROM @NewData) BEGIN
--We use MERGE here as it allows us to OUTPUT columns that weren't inserted into the table
MERGE INTO dbo.Data USING (SELECT ND.ID,
CASE ND.ID WHEN @BranchToCopy THEN @CopysParent ELSE K.NewID END AS Parent,
ND.SomeValue,
ND.Level
FROM @NewData ND
LEFT JOIN @Keys K ON ND.ParentID = K.OldID
WHERE ND.Level = @i) U ON 0=1
WHEN NOT MATCHED THEN
INSERT (ParentID, SomeValue)
VALUES (U.Parent, U.SomeValue)
OUTPUT U.ID, inserted.ID, U.Level
INTO @Keys (OldID, NewID, Level);
--Increment
SET @i = @i + 1;
END;
--Uncomment to see results
--SELECT *
--FROM dbo.[Data];
--Now we need to do the FullPath, as that would be a pain to do on the fly
DECLARE @Paths table (ID int, NewPath varchar(255));
--Work out the new paths
WITH rCTE AS(
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
CONVERT(varchar(255),NULL) AS NewPath
FROM dbo.Data D
WHERE D.ID = @CopysParent
UNION ALL
SELECT D.ID,
D.ParentID,
D.SomeValue,
D.FullPath,
CONVERT(varchar(255),CONCAT(ISNULL(r.FullPath,r.NewPath),'/(',D.ID,')'))
FROM dbo.Data D
JOIN rCTE r ON D.ParentID = r.ID
JOIN @Keys K ON D.ID = K.NewID) --As we want only the new rows
INSERT INTO @Paths (ID, NewPath)
SELECT ID, NewPath
FROM rCTe
WHERE FullPath IS NULL;
--Update the table
UPDATE D
SET FullPath = P.NewPath
FROM dbo.Data D
JOIN @Paths P ON D.ID = P.ID;
SELECT *
FROM dbo.Data;
GO
--Clean up
DROP TABLE dbo.Data;
这是一个仅使用 CTE 的解决方案:
路径 config as ( ...
定义了用于计算的 from 和 to id。这一切都可以在 TVF 中完成。
WITH T AS (
select 1 id, null parentid, 'A' somevalue, '(1)' fullpath union all
select 2 id, 1 parentid, 'A.1' somevalue, '(1)/(2)' fullpath union all
select 3 id, 2 parentid, 'A.1.1' somevalue, '(1)/(2)/(3)' fullpath union all
select 4 id, NULL parentid, 'B' somevalue, '(4)' fullpath union all
select 5 id, 4 parentid, 'B.1' somevalue, '(4)/(5)' fullpath union all
select 6 id, 4 parentid, 'B.2' somevalue, '(4)/(6)' fullpath union all
select 7 id, 6 parentid, 'B.2.1' somevalue, '(4)/(6)/(7)' fullpath
)
, config as (
select 4 from_id, 2 to_id
)
, maxid as (
select max(id) maxid from t
)
, initpath as (
select fullpath from t cross join config where id = to_id
)
, subset_from as (
select t.*, maxid + ROW_NUMBER() over (order by id) new_id, ROW_NUMBER() over (order by id) rn from t cross join config cross join maxid where fullpath like '(' + cast(from_id as varchar) + ')%'
)
, subset_count as (
select count(*) subset_count from subset_from
)
, fullpath_replacements (id, parentid, somevalue, new_id, fullpath, new_fullpath, lvl) as (
select id, parentid, somevalue, new_id, fullpath, replace(fullpath, '(' + cast((select sf.id from subset_from sf where rn = 1) as varchar) + ')', '(' + cast((select sf.new_id from subset_from sf where rn = 1) as varchar) + ')'), 1
from subset_from
union all
select id, parentid, somevalue, new_id, fullpath, replace(new_fullpath, '(' + cast((select sf.id from subset_from sf where sf.rn = fr.lvl + 1) as varchar) + ')', '(' + cast((select sf.new_id from subset_from sf where sf.rn = fr.lvl + 1) as varchar) + ')'), fr.lvl + 1
from fullpath_replacements fr where fr.lvl < (select subset_count from subset_count)
)
, final_replacement as (
select id, parentid, somevalue, new_id, fullpath, (select fullpath from t where t.id = (select to_id from config)) + '/' + new_fullpath new_fullpath, isnull((select sf.new_id from subset_from sf where sf.id = fr.parentid), (select to_id from config)) new_parentid
from fullpath_replacements fr where fr.lvl = (select subset_count from subset_count)
)
select id, parentid, somevalue, fullpath
from (
select * from t
union all
select new_id, new_parentid, somevalue, new_fullpath from final_replacement
) t order by id
想法是使用 row_number window 函数创建新的 ID(请参阅 subset_from
部分)。
然后通过 id 在完整路径 id 中进行替换。这是使用递归 CTE fullpath_replacements
来模拟循环来完成的。
这是可行的,因为在完整路径中我总是可以使用括号来标识需要交换完整路径的哪一部分。
这是输出: