我使用邻接列表模型在我的数据库中存储嵌套列表。每个列表可能有50-150个节点,因此我们平均称它为100个节点。用户想要克隆列表的情况已经出现(即,使用现有列表作为创建新列表的模板)。当新列表与现有列表略有不同时,此用例可能会节省大量时间。
这是我正在使用的表模式的缩写版本:
CREATE TABLE Nodes (
NodeId int IDENTITY(1,1) NOT NULL,
ParentId int NULL,
ListId int NOT NULL,
NodeText varchar(255) NOT NULL
)
我最初的想法是使用INSERT ... SELECT
一次复制所有节点,但这会留下引用旧ParentId
值的新记录。
我有一个有效的解决方案(在应用程序代码中,而不是SQL),但由于所需的查询数量,它似乎不是最理想的。这是算法:
ListId
添加到新列表。@@IDENTITY
,并将其与当前行的数据一起存储。Nodes
表,将ParentId
设置为新ID(来自上一步),其中ParentId
等于旧ID且ListId
等于新名单ID。就像我说的那样,工作正常,但它需要300多个查询来克隆包含100个节点的单个列表。有没有更有效的方法来实现同样的事情?
答案 0 :(得分:1)
试试这个。以下解决方案是零循环,零临时表。
<强> SQLFiddle 强>
DECLARE @CurrentID int = IDENT_CURRENT('Nodes'),
@OldListId int = 1,
@NewListId int;
SELECT @NewListId = ISNULL(MAX(ListId) ,0)+1 FROM Nodes
SET IDENTITY_INSERT Nodes ON
;WITH NewNode as (
SELECT ROW_NUMBER() OVER(ORDER BY NodeId)+ @CurrentID as NewNodeId, *
FROM Nodes WHERE ListId= @OldListId
)
INSERT INTO Nodes(NodeId,ParentId,ListId,NodeText)
SELECT N1.NewNodeId ,N2.NewNodeId , @NewListId, N1.NodeText FROM NewNode N1 LEFT OUTER JOIN NewNode N2 ON
N1.ParentId = N2.NodeId
--SELECT N1.* , N2.NewNodeId as NewParentId FROM NewNode N1 LEFT OUTER JOIN NewNode N2 ON
--N1.ParentId = N2.NodeId
SET IDENTITY_INSERT Nodes OFF
上述解决方案生成树,然后插入表中。请注意使用适当的事务和锁定机制来确保数据一致
答案 1 :(得分:0)
通过适当的事务包装和隔离,以下内容可满足您的需求:
-- Set up some sample data.
declare @Nodes as Table ( NodeId Int Identity, ParentId Int Null, ListId Int, NodeText VarChar(255) );
insert into @Nodes ( ParentId, ListId, NodeText ) values
( NULL, 1, '1' ),
( 1, 1, '1.1' ), ( 1, 1, '1.2' ),
( NULL, 2, '2' ),
( 4, 2, '2.1' ), ( 5, 2, '2.1.1' );
select * from @Nodes;
declare @TemplateListId as Int = 2;
-- Assuming that the clone is a new List. This is not important to what follows.
declare @ListId as Int = Coalesce( ( select Max( ListId ) from @Nodes ), 0 ) + 1;
-- Copy the template rows into the table and generate a mapping from old to new NodeIds.
declare @Fixups as Table ( OldNodeId Int, NewNodeId Int );
with Template as (
select NodeId, ParentId, ListId, NodeText
from @Nodes
where ListId = @TemplateListId )
merge into @Nodes as Nodes
using Template as T
on 42 < 0
when not matched then
insert ( ParentId, ListId, NodeText ) values ( ParentId, @ListId, NodeText )
output T.NodeId, inserted.NodeId into @Fixups;
select * from @Nodes;
select * from @Fixups;
-- Apply the fixups to the new copy.
update Nodes
set ParentId = Fixups.NewNodeId
from @Nodes as Nodes inner join
-- Update only the copy and not the template. (Could also use IN or EXISTS.)
@Fixups as Copy on Copy.NewNodeId = Nodes.NodeId inner join
-- Map the old nodes to the new.
@Fixups as Fixups on Fixups.OldNodeId = Nodes.ParentId;
select * from @Nodes;