This is as close to set-based as I can make it. The issue is that we cannot know what new identity values will be assigned until the rows are actually in the table. As such, there's no way to insert all rows in one go, with correct parent values.
I'm using MERGE
below so that I can access both the source and inserted
tables in the OUTPUT
clause, which isn't allowed for INSERT
statements:
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
(I've also written this on the assumption that we don't ever want to have rows in the table with wrong or missing parent values)
In case the logic isn't clear - we first find rows for the old user which have no parent - these we can clearly copy for the new user immediately. On the basis of this insert, we track what new identity values have been assigned against which old identity value.
We then continue to use this information to identify the next set of rows to copy (in @ToCopy
) - as the rows whose parents were just copied are the next set eligible to copy. We loop around until we produce an empty set, meaning all rows have been copied.
This doesn't cope with parent/child cycles, but hopefully you do not have any of those.