2010-01-26 5 views
8

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

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

БД является Oracle 11g, а также конкретное решение оракул хорошо ...

+0

Не совсем ответ, но Вы рассмотрели с помощью PERL или питона скрипт для обработки шага? – Pace

+0

Я боюсь, что SQL-древовидная структура действительно похожа на квадратную привязку к круглой дыре. Возможность использования только чрезмерного количества грубой силы. –

+0

Не ответ на ваш вопрос, но есть ли какая-то конкретная причина, связанная с отношениями родителя/ребенка в отдельной таблице? Если каждый термин может иметь только один родитель, тогда таблица терминов может иметь родительский столбец. Когда вам нужно найти детей, вы можете использовать инструкцию «выбрать-из-за-соединитесь с». Это также сделало бы корневые узлы более очевидными, так как их родительский столбец был бы нулевым. – Aaron

ответ

3

Обзор

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

Каждое из этих решений предполагает, что базы данных А и В имеют следующие таблицы:

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

create table Relationships 
(
    ParentID int, 
    ChildID int 
) 

раствор 1

Это самое простое решение. Его следует использовать, если:

  • Условия с одинаковым текстом, могут быть объединены вместе

Ниже будет объединить все условия и отношения из А в В:

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 

Solution 3

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

  • Условие с одинаковым текстом должно быть различно, и
  • Вы не можете изменить таблицу назначения и
  • Либо (а) вашего идентификатор столбца столбец идентификаторов (в Oracle, это означает, что он имеет триггер, который использует последовательность), или (б) вы хотите общий метод, который будет работать с любой технологией баз данных

Ниже будет объединить все термины и RELAT ionships из А в В:

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 

Solution 4

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

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

Ниже будет объединить все условия и отношения из а в в:

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

Oracle не имеет ничего эквивалентного столбцам идентификации (по крайней мере, это не было, когда я в последний раз его использовал). Это может иметь отношение к SQL Server, но вам не нужно будет делать это в Oracle. – ConcernedOfTunbridgeWells

+0

На самом деле Oracle имеет эквивалент столбцам идентификации: триггер вместе с последовательностью. Это будет хорошо продуманная база данных. Visage, возможно, потребуется использовать мое решение 3 в Oracle, если у него нет доступа к последовательности, которая генерирует значение ID. Также в общих случаях он может справиться с «решением 1» или «решением 2», которые являются более простыми и эффективными, поскольку они не создают временную таблицу. В ситуациях, когда последовательность * доступна *, я добавил «Решение 4», которое показывает, как использовать его, чтобы избежать итерации. –

5

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

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

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

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

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

В 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 с аналогичным объединением или вставленный непосредственно в таблицу назначения из запроса, аналогичного запросу выше.

0

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

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

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

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

HIERARCHY[idparent, idchild] 

То, что вы хотите сделать, это первый экземпляр ПУНКТЫ из SourceDB в TargetDB, выпускающие TargetDB создавать свои собственные значения для столбца идентификаторов.

Затем вам нужно скопировать ИЕРАРХИЯ из SourceDB в TargetDB, но вы должны сделать присоединиться как так, чтобы получить новый идентификатор:

И вы должны сделать то же самое для столбца 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 

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

0

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

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

Создать временную таблицу в TargetDB с этим Структура:
TempTerms: OldId, Текст, OldParentId, NewID, NewParentId

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

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

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

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

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

(I может быть недоразумение вопроса здесь, но я надеюсь, что нет.)