Экспорт / Импорт иерархического графика из базы данных - PullRequest
8 голосов
/ 26 января 2010

У меня есть базовая схема БД, состоящая из 2 таблиц; Одним из них является простой идентификатор -> текстовый список терминов, а другой имеет 2 столбца, родительский и дочерний. Идентификаторы в первой таблице генерируются при вставке с помощью последовательности дб, в то время как вторая таблица содержит отображение между ключами для хранения «структуры» иерархии.

Моя проблема в том, что я иногда хочу переместить дерево из одной базы данных в другую. Если у меня есть 2 БД, каждая из которых содержит 10 терминов (Термины базы данных A! = Термины базы данных B, и нет дублирования), и я просто скопирую данные из A в B, тогда у меня возникнет очевидная проблема: перенумерованы, но отношения не будут. Очевидно, что в этом примере сработает только добавление 10 ко всем ключам взаимосвязей, но кто-нибудь знает общий алгоритм для этого?

БД - oracle 11g, а решение для Oracle - прекрасно ...

Ответы [ 5 ]

5 голосов
/ 29 января 2010

Быстрый ответ

Импорт в промежуточную таблицу, но заполнение отображенных значений идентификаторов из той же последовательности , используемой для получения значений идентификаторов из таблицы назначения. Это гарантированно позволяет избежать конфликтов между значениями идентификаторов, поскольку механизм СУБД поддерживает одновременный доступ к последовательностям.

При отображении значений идентификаторов на узле (см. Ниже) повторное отображение значений идентификаторов для ребер становится тривиальным.

Более длинный ответ

Вам понадобится механизм, который сопоставляет значения между старыми ключами из источника и новыми ключами в месте назначения. Способ сделать это состоит в том, чтобы создать промежуточные промежуточные таблицы, которые содержат сопоставления между старым и новым кейями.

В Oracle автоинкрементные ключи обычно выполняются с последовательностями во многом так, как вы описали. Вам необходимо создать промежуточные таблицы с заполнителем для «старого» ключа, чтобы вы могли выполнить повторное сопоставление. Используйте ту же последовательность, что и в приложении, для заполнения значений идентификаторов в реальных таблицах базы данных назначения. СУБД обеспечивает одновременный доступ к последовательностям, а использование одной и той же последовательности гарантирует, что вы не получите коллизий в отображенных значениях идентификатора.

Если у вас есть такая схема:

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, сохраняя импортированные значения ключа. Процесс вставки помещает исходный идентификатор из импортированной таблицы в STAGED_ID и заполняет идентификатор из последовательности.

Убедитесь, что вы используете ту же последовательность, которая используется для заполнения столбца идентификатора в таблица назначения. Это гарантирует, что вы не будете получить ключевые столкновения, когда вы идете в вставить в окончательную таблицу назначения. Важно повторно использовать ту же последовательность.

В качестве полезного побочного эффекта это также позволит запускать импорт, пока другие операции выполняются в таблице; одновременные чтения в одной последовательности - это нормально. При необходимости вы можете запустить этот тип процесса импорта, не отключая приложение.

Когда у вас есть это отображение в промежуточной таблице, значения ID в таблице EDGE легко вычислить с помощью запроса, подобного следующему:

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 

Отображенные значения EDGE можно заполнить обратно в промежуточные таблицы с помощью запроса UPDATE с аналогичным соединением или вставить непосредственно в таблицу назначения из запроса, аналогичного приведенному выше.

3 голосов
/ 29 января 2010

Обзор

Я дам четыре решения, начиная с самого простого. С каждым решением я объясню ситуации, в которых оно будет применимо.

В каждом из этих решений предполагается, что базы данных 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

Это следующее простейшее решение. Следует использовать, если:

  • Термины с тем же текстом должны быть различны, а
  • Вы можете добавить столбец в таблицу назначения

Сначала добавьте столбец int в таблицу «Термины» с именем «OldID», а затем используйте следующее для объединения всех терминов и отношений от А до В:

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

Решение 3

Это решение использует итерацию. Следует использовать, если:

  • Термины с одним и тем же текстом должны быть различны, а
  • Вы не можете изменить таблицу назначения, а
  • Либо (a) ваш столбец идентификатора является столбцом идентификаторов (в Oracle это означает, что у него есть триггер, использующий последовательность), либо (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

Решение 4

Это решение специфично для Oracle, требует, чтобы вы знали последовательность, используемую для генерации значений идентификаторов, и менее эффективно, чем некоторые другие решения. Следует использовать, если:

  • Термины с одним и тем же текстом должны быть различны, а
  • Вы не можете изменить таблицу назначения, а
  • У вас есть доступ к последовательности, которая генерирует столбец идентификатора, и
  • Вы в порядке, используя технику, которая не будет портировать на не-Oracle-технологию базы данных

Следующее объединит все термины и отношения из 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
0 голосов
/ 02 февраля 2010

А как насчет передачи данных в виде XML? Он, естественно, предназначен для работы с древовидными структурами, и многие СУБД включают хорошую поддержку для анализа и преобразования XML.

Вам нужно будет убедиться, что узел X в DB1 отображается на узел Y в DB 2, но для этого вам нужно использовать некоторые факты об узле (имя и т. Д.) За пределами первичного ключа.

Вы также можете сместить ключи для каждой БД на обычную величину (скажем, 2 ^ 32) и использовать БОЛЬШУЮ клавишу INTEGER. Ограничивает записи до 2 ^ 32, но все же полезно.

(Я могу неправильно понять вопрос здесь, но надеюсь, что нет.)

0 голосов
/ 29 января 2010

Вы можете достичь того, что вам нужно, используя временную таблицу в целевой базе данных. Поскольку идентификаторы генерируются автоматически, следующий код не будет создавать никаких коллизий.

Я собираюсь предположить, что исходная база данных называется SourceDb, а целевая база данных называется TargetDb. Я также собираюсь предположить эту структуру таблицы:
Условия : ID , Текст
Отношения : ParentId , ChildId

Создать временную таблицу в TargetDb с этой структурой:
TempTerms : Старый идентификатор , Текст , Старый идентификатор родителя , Новый идентификатор , Новый идентификатор родителя

Следующий код скопирует ваше поддерево в целевую базу данных.

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;
0 голосов
/ 29 января 2010

Раньше я часто делал подобные вещи, но моя память немного туманная. Я дам вам общую идею, надеюсь, она поможет вам в правильном направлении.

По сути, вы можете сделать это, только если у вас есть надежный второй столбец «уникальный ключ» в таблице «родитель». Если нет, вам нужно создать его.

Скажем, у нас есть эти таблицы

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

HIERARCHY[idparent, idchild]

Что вы хотите сделать, так это сначала скопировать ITEMS из SOURCEDB в TARGETDB, позволяя TARGETDB создавать свои собственные значения для столбца id.

Затем вам нужно скопировать HIERARCHY из SOURCEDB в TARGETDB, но вам нужно выполнить соединение, как это, чтобы получить новый идентификатор:

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

И вам нужно сделать то же самое для столбца idchild.

Это даст что-то вроде этого (непроверенный, ржавый и, вероятно, синтаксис mssql):

//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

Я предполагаю, что эти две базы данных достаточно "связаны", чтобы вы могли выполнять запросы между базами данных. Если вам нужно сериализовать в файл, он становится немного ... сложнее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...