代码之家  ›  专栏  ›  技术社区  ›  pauljwilliams

从数据库导出/导入分层图

  •  8
  • pauljwilliams  · 技术社区  · 16 年前

    我有一个基本的数据库模式,包括2个表;一个是简单的ID->术语的文本列表,另一个有两列,父列和子列。第一个表中的ID在insert时由db序列生成,而第二个表包含键之间的映射以存储层次结构的“结构”。

    我的问题是,有时我可能想将树从一个db移动到另一个db。如果我有2个数据库,每个数据库中有10个术语(数据库A的术语!=数据库B的术语,没有重叠),我只是将数据从A复制到B,然后我会发现一个明显的问题,术语会重新编号,但关系不会。显然,在本例中,只需向所有关系键添加10个键就可以了,但是有人知道这样做的通用算法吗?

    5 回复  |  直到 16 年前
        1
  •  3
  •   Ray Burns    16 年前

    概述

    我将给出四个解决方案,从最简单的开始。对于每个解决方案,我将解释其适用的情况。

    这些解决方案都假设数据库A和B具有以下表格:

    create table Terms
    (
      ID int identity(1,1),
      Text nvarchar(MAX)
    )
    
    create table Relationships
    (
      ParentID int,
      ChildID int
    )
    

    解决方案1

    这是最简单的解决办法。应在以下情况下使用:

    • 文本相同的术语可以合并在一起

    以下内容将把A中的所有术语和关系合并到B中:

    insert into A.Terms (Text)
      select Text
      from A.Terms
      where Text not in (select Text from B.Terms)
    
    insert into B.Relationships (ParentID, ChildID)
      select
        (select ID
         from B.Terms BTerms inner join A.Terms ATerms on BTerms.Text = ATerms.Text
         where ATerms.ID = Relationships.ParentID),
        (select ID
         from B.Terms BTerms inner join A.Terms ATerms on BTerms.Text = ATerms.Text
         where ATerms.ID = Relationships.ChildID)
      from A.Relationships
    

    where 第一条 insert into

    解决方案2

    这是下一个最简单的解决方案。应在以下情况下使用:

    • 具有相同文本的术语必须保持不同,并且

    首先在术语表中添加一个名为“OldID”的int列,然后使用以下命令将所有术语和关系从A合并到B:

    insert into A.Terms (Text, OldID)
      select Text, ID
      from A.Terms
      where Text not in (select Text from B.Terms)
    
    insert into B.Relationships (ParentID, ChildID)
      select
        (select ID from B.Terms where OldID = ParentID),
        (select ID from B.Terms where OldID = ChildID)
      from A.Relationships
    

    此解决方案使用迭代。应在以下情况下使用:

    • 具有相同文本的术语必须保持不同,并且
    • 无法修改目标表,并且
    • (a) (b) 您需要一种通用的方法来处理任何数据库技术

    以下内容将把A中的所有术语和关系合并到B中:

    declare TermsCursor sys_refcursor; 
    begin 
    
    -- Create temporary mapping table
    create table #Temporary (OldID int, NewID int)
    
    -- Add terms one at a time, remembering the id mapping
    open TermsCursor for select * from A.Terms;
    for term in TermsCursor 
    loop
      insert into B.Terms (Text) values ( term.Text ) returning ID into NewID;
      insert into Temporary ( OldID, NewID ) values ( term.ID, NewID );
    end loop; 
    
    -- Transfer the relationships
    insert into B.Relationships (ParentID, ChildID)
      select
        (select ID
         from B.Terms BTerms inner join Temporary on BTerms.ID = Temporary.NewID
         where Temporary.OldID = Relationships.ParentID),
        (select ID
         from B.Terms BTerms inner join Temporary on BTerms.ID = Temporary.NewID
         where Temporary.OldID = Relationships.ChildID),
      from A.Relationships
    
    -- Drop the temporary table
    drop table #Temporary
    
    end
    

    此解决方案是特定于Oracle的,需要您知道用于生成ID值的顺序,并且效率低于其他一些解决方案。应在以下情况下使用:

    • 无法修改目标表,并且
    • 您可以访问生成ID列的序列,并且

    以下内容将把A中的所有术语和关系合并到B中:

    -- Create temporary mapping table
    create table #Temporary (OldID int, NewID int)
    
    -- Add terms to temporary mapping table
    insert into #Tempoarary ( OldID, NewID )
    select ID, sequence.nexval
    from A.Terms
    
    -- Transfer the terms
    insert into B.Terms ( ID, Text )
    select NewID, Text
    from A.Terms inner join Temporary on ID = OldID
    
    -- Transfer the relationships
    insert into B.Relationships (ParentID, ChildID)
      select
        (select ID
         from B.Terms BTerms inner join Temporary on BTerms.ID = Temporary.NewID
         where Temporary.OldID = Relationships.ParentID),
        (select ID
         from B.Terms BTerms inner join Temporary on BTerms.ID = Temporary.NewID
         where Temporary.OldID = Relationships.ChildID),
      from A.Relationships
    
    -- Drop the temporary table
    drop table #Temporary
    
        2
  •  5
  •   ConcernedOfTunbridgeWells    16 年前

    快速回答

    导入到临时表中,但填充映射的ID值 用于从目标表生成ID值。这可以保证避免ID值之间的冲突,因为DBMS引擎支持对序列的并发访问。

    当节点上的ID值已映射(请参见下文)时,重新映射边的ID值非常简单。

    您需要一种机制来映射源中的旧键和目标中的新键之间的值。实现这一点的方法是创建包含新旧KAY之间映射的中间暂存表。

    在Oracle中,自动递增键通常以您描述的方式使用序列来完成。您需要使用“old”键的占位符来构造临时表,以便可以重新映射。使用与应用程序用于填充实际目标数据库表上的ID值相同的顺序。DBMS允许对序列进行并发访问,并且使用相同的序列可以保证在映射的ID值中不会发生冲突。

    如果您的模式类似于:

    create table STAGE_NODE (
           ID int
          ,STAGED_ID int
    )
    /
    
    create table STAGE_EDGE (
           FROM_ID   int
          ,TO_ID     int
          ,OLD_FROM_ID int
          ,OLD_TO_ID int
    )
    /
    

    这将允许您导入到 STAGE_NODE

    用于填充中的ID列 目标表。这确保了您不会 插入到最终目标表。 重复使用相同的序列很重要。

    作为一个有用的副作用,这还将允许在表上进行其他操作时运行导入;对单个序列进行并发读取是可以的。如有必要,您可以在不关闭应用程序的情况下运行这种类型的导入过程。

    一旦在暂存表中有了此映射,边缘表中的ID值就可以通过以下查询进行计算:

    select node1.ID         as FROM_ID
          ,node2.ID         as TO_ID
      from STAGE_EDGE se
      join STAGE_NODE node1
        on node1.STAGED_ID = se.OLD_FROM_ID
      join STAGE_NODE node2
        on node2.STAGED_ID = se.OLD_TO_ID 
    

    可以使用具有类似联接的更新查询将映射的边缘值填充回暂存表,也可以从类似于上述查询的查询直接插入到目标表中。

        3
  •  0
  •   Benjol    16 年前

    我过去经常做这种事,但我的记忆有点模糊。我会给你一个大概的想法,希望它能为你指明正确的方向。

    基本上,只有在“parent”表中有可靠的第二个“unique key”列时,才能执行此操作。如果没有,则需要创建一个。

    假设我们有这些桌子

    ITEMS[id, A, key] //id: 'real' id, A: just some column, key: the alternate key
    
    HIERARCHY[idparent, idchild]
    

    您要做的是首先将项目从SOURCEDB复制到TARGETDB,让TARGETDB为id列创建自己的值。

    SOURCEDB.HIERARCHY.idparent 
          -> SOURCEDB.ITEMS.id 
          -> SOURCEDB.ITEMS.key 
          -> TARGETDB.ITEMS.key 
          -> TARGETDB.ITEMS.id
    

    您需要对idchild列执行相同的操作。

    //step 1
    INSERT TARGETDB.ITEMS(A, key)
    SELECT A, key FROM SOURCEDB.ITEMS
    
    //step 2
    INSERT TARGETDB.HIERARCHY(idparent, idchild)
    SELECT T1.id, T2.id
    FROM SOURCEDB.HIERARCHY AS H1
         INNER JOIN SOURCEDB.ITEMS AS I1 ON H1.idparent = I1.id
         INNER JOIN TARGETDB.ITEMS AS T1 ON I1.key = T1.key
         INNER JOIN SOURCEDB.ITEMS AS I2 ON H1.idchild = I2.id
         INNER JOIN TARGETDB.ITEMS AS T2 ON I2.key = T2.key
    

    我假设这两个数据库足够“连接”,可以进行跨数据库查询。如果您必须序列化到文件,它会得到更多。。。复杂。

        4
  •  0
  •   Aaron    16 年前

    您可以使用目标数据库中的临时表来实现所需的功能。 由于ID是自动生成的,下面的代码不会生成任何冲突。

    我假设源数据库名为SourceDb,目标数据库名为TargetDb。 我还要假设这个表结构:
    条款 身份证件 , 正文
    : ,

    使用以下结构在TargetDb中创建临时表:
    诱惑 : 旧的 , 正文 , , 纽伊德 , 新父ID

    下面的代码将您的子树复制到目标数据库。

    declare
        RootOfSubtreeId SourceDb.Terms.Id%type;
        TermCursor sys_refcursor;
    begin
        --//Copy the data from SourceDb into the TargetDb temp table.
        --//This query gets the entire subtree of data with the root of the subtree having ID=RootOfSubTreeId.
        insert into TargetDb.TempTerms
        (
            OldId, Text, OldParentId
        )
        with RelatedTerms as
        (
            select
                T.ID, T.Text, R.ParentId
            from
                SourceDb.Terms T
                join SourceDb.Relationships R
                on R.ChildId = T.ID
        )
        select
            ID,
            Text,
            ParentId
        from
            RelatedTerms
        connect by
            prior ID = ParentId
        start with
            ID = RootOfSubtreeId;
    
        --//Open a cursor to loop over all of the temporary data.
        open TermCursor for
        select
            *
        from
            TargetDb.TempTerms;
    
        for term in TermCursor
        loop
            --//Insert the item into TargetDb's Terms table and get the new id back.
            insert into TargetDb.Terms
            ( ID, Text )
            values
            ( term.Text )
            returning ID into NewTermId;
    
            --//Update the temp table's NewId column for the newly inserted row.
            update TargetDb.TempTerms
            set    NewId = NewTermId
            where  OldId = term.OldId;
    
            --//Update the temp table's NewParentId column for all children of the newly inserted row.
            update TargetDb.TempTerms
            set    NewParentId = NewTermId
            where  OldParentId = term.OldId;
        end loop;
    
        --//Add all relationship data to TargetDb using the new IDs found above.
        insert into TargetDb.Relationships
        ( ParentId, ChildId )
        select
            NewParentId, NewId
        from
            TargetDb.TempTerms
        where
            NewParentId is not null;
    end;
    
        5
  •  0
  •   BobMcGee    16 年前

    如何将数据作为XML传递?它自然地被设计为使用树结构,并且许多DBMS包括对XML解析和转换的良好支持。

    您必须确保DB1中的节点X映射到DB 2中的节点Y,但为此,您应该在主键之外使用关于节点的一些事实(名称等)。

    您还可以将每个DB的键偏移常规量(例如2^32),并使用大整数键。将条目限制为2^32,但仍然有用。