在 SQL Server 2008 中递归
Recursive in SQL Server 2008
我有这样一个场景(table):
这是table(文件夹)结构。 我在这个 table 中只有 user_id = 1 的记录。现在我需要为另一个用户插入相同的文件夹结构。
抱歉,我已经更新了问题...
是的,folder_id 是身份列(但是 folder_id 可以针对特定的用户 ID 进行网格化)。考虑到我不知道可以存在多少个子文件夹。
Folder_Names 对一个用户来说是唯一的,文件夹结构对所有用户来说都不相同。假设 user3 需要与 user1 相同的文件夹结构,而 user4 需要与 user2 相同的文件夹结构。
并且我将只提供源用户 ID 和目标用户 ID(假设目标用户 ID 没有任何文件夹结构)。
我怎样才能做到这一点?
您可以执行以下操作:
SET IDENTITY_INSERT dbo.Folder ON
go
declare @maxFolderID int
select @maxFolderID = max(Folder_ID) from Folder
insert into Folder
select @maxFolderID + FolderID, @maxFolderID + Parent_Folder_ID, Folder_Name, 2
from Folder
where User_ID = 1
SET IDENTITY_INSERT dbo.Folder OFF
go
编辑:
SET IDENTITY_INSERT dbo.Folder ON
GO
;
WITH m AS ( SELECT MAX(Folder_ID) AS mid FROM Folder ),
r AS ( SELECT * ,
ROW_NUMBER() OVER ( ORDER BY Folder_ID ) + m.mid AS rn
FROM Folder
CROSS JOIN m
WHERE User_ID = 1
)
INSERT INTO Folder
SELECT r1.rn ,
r2.rn ,
r1.Folder_Name ,
2
FROM r r1
LEFT JOIN r r2 ON r2.Folder_ID = r1.Parent_Folder_ID
SET IDENTITY_INSERT dbo.Folder OFF
GO
这已经是我能做到的最接近集合的了。问题在于,在行实际位于 table 中之前,我们无法知道将分配哪些新标识值。因此,无法使用正确的父值一次性插入所有行。
我在下面使用 MERGE
,这样我就可以在 OUTPUT
子句中访问源和 inserted
table,这是不允许的INSERT
语句:
declare @FromUserID int
declare @ToUserID int
declare @ToCopy table (OldParentID int,NewParentID int)
declare @ToCopy2 table (OldParentID int,NewParentID int)
select @FromUserID = 1,@ToUserID = 2
merge into T1 t
using (select Folder_ID,Parent_Folder_ID,Folder_Name
from T1 where User_ID = @FromUserID and Parent_Folder_ID is null) s
on 1 = 0
when not matched then insert (Parent_Folder_ID,Folder_Name,User_ID)
values (NULL,s.Folder_Name,@ToUserID)
output s.Folder_ID,inserted.Folder_ID into @ToCopy (OldParentID,NewParentID);
while exists (select * from @ToCopy)
begin
merge into T1 t
using (select Folder_ID,p2.NewParentID,Folder_Name from T1
inner join @ToCopy p2 on p2.OldParentID = T1.Parent_Folder_ID) s
on 1 = 0
when not matched then insert (Parent_Folder_ID,Folder_Name,User_ID)
values (NewParentID,Folder_Name,@ToUserID)
output s.Folder_ID,inserted.Folder_ID into @ToCopy2 (OldParentID,NewParentID);
--This would be much simpler if you could assign table variables,
-- @ToCopy = @ToCopy2
-- @ToCopy2 = null
delete from @ToCopy;
insert into @ToCopy(OldParentID,NewParentID)
select OldParentID,NewParentID from @ToCopy2;
delete from @ToCopy2;
end
(我还假设我们不想在 table 中有父值错误或缺失的行)
如果逻辑不清楚 - 我们首先找到没有父级的旧用户的行 - 我们可以立即为新用户清楚地复制这些行。在此插入的基础上,我们跟踪针对哪个旧标识值分配了哪些新标识值。
然后我们继续使用此信息来识别下一组要复制的行(在 @ToCopy
中)- 因为其父项刚刚被复制的行是下一组有资格复制的行。我们循环直到产生一个空集,这意味着所有行都已被复制。
这无法应对 parent/child 个循环,但希望您没有这些循环。
假设 Folder.Folder_ID
是一个标识列,您最好分两步执行此操作,第一步是插入您需要的文件夹,下一步是更新父文件夹 ID。
DECLARE @ExistingUserID INT = 1,
@NewUserID INT = 2;
BEGIN TRAN;
-- INSERT REQUIRED FOLDERS
INSERT Folder (Folder_Name, User_ID)
SELECT Folder_Name, User_ID = @NewUserID
FROM Folder
WHERE User_ID = @ExistingUserID;
-- UPDATE PARENT FOLDER
UPDATE f1
SET Parent_Folder_ID = p2.Folder_ID
FROM Folder AS f1
INNER JOIN Folder AS f2
ON f2.Folder_Name = f1.Folder_Name
AND f2.user_id = @ExistingUserID
INNER JOIN Folder AS p1
ON p1.Folder_ID = f2.Parent_Folder_ID
INNER JOIN Folder AS p2
ON p2.Folder_Name = p1.Folder_Name
AND p2.user_id = @NewUserID
WHERE f1.user_id = @NewUserID;
COMMIT TRAN;
解决方案 2
DECLARE @Output TABLE (OldFolderID INT, NewFolderID INT, OldParentID INT);
DECLARE @ExistingUserID INT = 1,
@NewUserID INT = 2;
BEGIN TRAN;
MERGE Folder AS t
USING
( SELECT *
FROM Folder
WHERE user_ID = @ExistingUserID
) AS s
ON 1 = 0 -- WILL NEVER BE TRUE SO ALWAYS GOES TO MATCHED CLAUSE
WHEN NOT MATCHED THEN
INSERT (Folder_Name, User_ID)
VALUES (s.Folder_Name, @NewUserID)
OUTPUT s.Folder_ID, inserted.Folder_ID, s.Parent_Folder_ID
INTO @Output (OldFolderID, NewFolderID, OldParentID);
-- UPDATE PARENT FOLDER
UPDATE f
SET Parent_Folder_ID = p.NewFolderID
FROM Folder AS f
INNER JOIN @Output AS o
ON o.NewFolderID = f.Folder_ID
INNER JOIN @Output AS p
ON p.OldFolderID = o.OldParentID;
COMMIT TRAN;
我有这样一个场景(table):
这是table(文件夹)结构。 我在这个 table 中只有 user_id = 1 的记录。现在我需要为另一个用户插入相同的文件夹结构。
抱歉,我已经更新了问题... 是的,folder_id 是身份列(但是 folder_id 可以针对特定的用户 ID 进行网格化)。考虑到我不知道可以存在多少个子文件夹。 Folder_Names 对一个用户来说是唯一的,文件夹结构对所有用户来说都不相同。假设 user3 需要与 user1 相同的文件夹结构,而 user4 需要与 user2 相同的文件夹结构。 并且我将只提供源用户 ID 和目标用户 ID(假设目标用户 ID 没有任何文件夹结构)。
我怎样才能做到这一点?
您可以执行以下操作:
SET IDENTITY_INSERT dbo.Folder ON
go
declare @maxFolderID int
select @maxFolderID = max(Folder_ID) from Folder
insert into Folder
select @maxFolderID + FolderID, @maxFolderID + Parent_Folder_ID, Folder_Name, 2
from Folder
where User_ID = 1
SET IDENTITY_INSERT dbo.Folder OFF
go
编辑:
SET IDENTITY_INSERT dbo.Folder ON
GO
;
WITH m AS ( SELECT MAX(Folder_ID) AS mid FROM Folder ),
r AS ( SELECT * ,
ROW_NUMBER() OVER ( ORDER BY Folder_ID ) + m.mid AS rn
FROM Folder
CROSS JOIN m
WHERE User_ID = 1
)
INSERT INTO Folder
SELECT r1.rn ,
r2.rn ,
r1.Folder_Name ,
2
FROM r r1
LEFT JOIN r r2 ON r2.Folder_ID = r1.Parent_Folder_ID
SET IDENTITY_INSERT dbo.Folder OFF
GO
这已经是我能做到的最接近集合的了。问题在于,在行实际位于 table 中之前,我们无法知道将分配哪些新标识值。因此,无法使用正确的父值一次性插入所有行。
我在下面使用 MERGE
,这样我就可以在 OUTPUT
子句中访问源和 inserted
table,这是不允许的INSERT
语句:
declare @FromUserID int
declare @ToUserID int
declare @ToCopy table (OldParentID int,NewParentID int)
declare @ToCopy2 table (OldParentID int,NewParentID int)
select @FromUserID = 1,@ToUserID = 2
merge into T1 t
using (select Folder_ID,Parent_Folder_ID,Folder_Name
from T1 where User_ID = @FromUserID and Parent_Folder_ID is null) s
on 1 = 0
when not matched then insert (Parent_Folder_ID,Folder_Name,User_ID)
values (NULL,s.Folder_Name,@ToUserID)
output s.Folder_ID,inserted.Folder_ID into @ToCopy (OldParentID,NewParentID);
while exists (select * from @ToCopy)
begin
merge into T1 t
using (select Folder_ID,p2.NewParentID,Folder_Name from T1
inner join @ToCopy p2 on p2.OldParentID = T1.Parent_Folder_ID) s
on 1 = 0
when not matched then insert (Parent_Folder_ID,Folder_Name,User_ID)
values (NewParentID,Folder_Name,@ToUserID)
output s.Folder_ID,inserted.Folder_ID into @ToCopy2 (OldParentID,NewParentID);
--This would be much simpler if you could assign table variables,
-- @ToCopy = @ToCopy2
-- @ToCopy2 = null
delete from @ToCopy;
insert into @ToCopy(OldParentID,NewParentID)
select OldParentID,NewParentID from @ToCopy2;
delete from @ToCopy2;
end
(我还假设我们不想在 table 中有父值错误或缺失的行)
如果逻辑不清楚 - 我们首先找到没有父级的旧用户的行 - 我们可以立即为新用户清楚地复制这些行。在此插入的基础上,我们跟踪针对哪个旧标识值分配了哪些新标识值。
然后我们继续使用此信息来识别下一组要复制的行(在 @ToCopy
中)- 因为其父项刚刚被复制的行是下一组有资格复制的行。我们循环直到产生一个空集,这意味着所有行都已被复制。
这无法应对 parent/child 个循环,但希望您没有这些循环。
假设 Folder.Folder_ID
是一个标识列,您最好分两步执行此操作,第一步是插入您需要的文件夹,下一步是更新父文件夹 ID。
DECLARE @ExistingUserID INT = 1,
@NewUserID INT = 2;
BEGIN TRAN;
-- INSERT REQUIRED FOLDERS
INSERT Folder (Folder_Name, User_ID)
SELECT Folder_Name, User_ID = @NewUserID
FROM Folder
WHERE User_ID = @ExistingUserID;
-- UPDATE PARENT FOLDER
UPDATE f1
SET Parent_Folder_ID = p2.Folder_ID
FROM Folder AS f1
INNER JOIN Folder AS f2
ON f2.Folder_Name = f1.Folder_Name
AND f2.user_id = @ExistingUserID
INNER JOIN Folder AS p1
ON p1.Folder_ID = f2.Parent_Folder_ID
INNER JOIN Folder AS p2
ON p2.Folder_Name = p1.Folder_Name
AND p2.user_id = @NewUserID
WHERE f1.user_id = @NewUserID;
COMMIT TRAN;
解决方案 2
DECLARE @Output TABLE (OldFolderID INT, NewFolderID INT, OldParentID INT);
DECLARE @ExistingUserID INT = 1,
@NewUserID INT = 2;
BEGIN TRAN;
MERGE Folder AS t
USING
( SELECT *
FROM Folder
WHERE user_ID = @ExistingUserID
) AS s
ON 1 = 0 -- WILL NEVER BE TRUE SO ALWAYS GOES TO MATCHED CLAUSE
WHEN NOT MATCHED THEN
INSERT (Folder_Name, User_ID)
VALUES (s.Folder_Name, @NewUserID)
OUTPUT s.Folder_ID, inserted.Folder_ID, s.Parent_Folder_ID
INTO @Output (OldFolderID, NewFolderID, OldParentID);
-- UPDATE PARENT FOLDER
UPDATE f
SET Parent_Folder_ID = p.NewFolderID
FROM Folder AS f
INNER JOIN @Output AS o
ON o.NewFolderID = f.Folder_ID
INNER JOIN @Output AS p
ON p.OldFolderID = o.OldParentID;
COMMIT TRAN;