克隆邻接列表模型中树的数据库表示的最有效方法

时间:2013-12-02 21:31:51

标签: sql sql-server performance algorithm tree

我使用邻接列表模型在我的数据库中存储嵌套列表。每个列表可能有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),但由于所需的查询数量,它似乎不是最理想的。这是算法:

  1. 选择属于旧列表的所有记录。
  2. 遍历行并通过插入不同的ListId添加到新列表。
  3. 从每个插页中选择@@IDENTITY,并将其与当前行的数据一起存储。
  4. 再次迭代行并更新Nodes表,将ParentId设置为新ID(来自上一步),其中ParentId等于旧ID且ListId等于新名单ID。
  5. 就像我说的那样,工作正常,但它需要300多个查询来克隆包含100个节点的单个列表。有没有更有效的方法来实现同样的事情?

2 个答案:

答案 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;