2010-01-26 4 views
8

Tengo un esquema básico de base de datos que comprende 2 tablas; Uno es un ID simple -> Lista de términos de texto, y el otro tiene 2 columnas, padre e hijo. Los ids en la primera tabla se generan en insert por una secuencia db, mientras que la segunda tabla contiene una asignación entre claves para almacenar la 'estructura' de la jerarquía.Exportación/importación de un gráfico jerárquico a partir de una base de datos

Mi problema es que a veces quiero mover un árbol de un db a otro. Si tengo 2 DB, cada uno con 10 términos en (términos de la Base de Datos A = términos de la Base de Datos B, y no hay superposición), y solo copio los datos de A a B entonces obtendré un problema obvio de que los términos serán renumerado pero las relaciones no. Claramente, en este ejemplo, solo agregar 10 a todas las claves de relación funcionará, pero ¿alguien sabe de un algoritmo general para hacer esto?

La base de datos es Oracle 11g, y una solución específica Oracle está bien ...

+0

realmente una respuesta, pero han considerado el uso de un script Perl o Python para manejar el movimiento? – Pace

+0

Me temo que SQL a las estructuras de árbol es realmente como una clavija cuadrada a un agujero redondo. Solo se puede hacer usando cantidades excesivas de fuerza bruta. –

+0

No es una respuesta a su pregunta, pero ¿hay alguna razón específica para tener sus relaciones padre/hijo en una tabla separada? Si cada término puede tener solo un padre, entonces la tabla de términos podría tener una columna principal. Cuando necesite encontrar niños, puede usar una declaración 'select-from-connect by-start with'. Esto también haría que los nodos raíz sean más obvios, ya que su columna Parent sería nula. – Aaron

Respuesta

3

general

daré cuatro soluciones, empezando por la más simple. Con cada solución explicaré las situaciones en las que sería aplicable.

Cada una de estas soluciones de bases de datos asume que A y B tienen las siguientes tablas:

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

create table Relationships 
(
    ParentID int, 
    ChildID int 
) 

solución al 1

Ésta es la solución más sencilla. Se debe utilizar si:

  • Palabras con texto idéntico se pueden combinar entre sí

A continuación se fusionarán todos los términos y las relaciones de A 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 

Básicamente primera copia los términos, luego copie las relaciones mapeando la identificación anterior a la nueva identificación basada en el texto.

Nota: En su pregunta indica que los términos son disjuntos entre las dos bases de datos de entrada. En ese caso, se puede omitir la cláusula where en el primer insert into.

Solución 2

Ésta es la solución más simple, al lado. Se debe utilizar si:

  • Palabras con el mismo texto deben mantenerse separados, y
  • Usted puede agregar una columna a la tabla de destino

Primero se debe agregar una columna int a su mesa Términos llamado "oldid", a continuación, utilizar el siguiente para combinar todos los términos y las relaciones de a 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 

Solución 3

Esta solución usa iteración.Se debe utilizar si:

  • Palabras con el mismo texto deben mantenerse separados, y
  • Usted no puede modificar la tabla de destino, y
  • De cualquier (a) su columna de ID es una columna de identidad (en Oracle, esto significa que tiene un disparador que utiliza una secuencia), o (b) desea un método general que funcionará con cualquier tecnología de base de datos

a continuación se fusionarán todos los términos y relat ionships de A en 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 

Solución 4

Esta solución es Oracle-específico, se requiere conocer la secuencia utilizada para generar valores de ID, y es menos eficiente que algunas de las otras soluciones . Debe utilizarse si:

  • Palabras con el mismo texto deben mantenerse separados, y
  • Usted no puede modificar la tabla de destino, y
  • tiene acceso a la secuencia que genera su columna de identificación, y
  • estás bien utilizando un techinique que no va a puerto a una tecnología de base de datos de Oracle no

a continuación se fusionarán todos los términos y las relaciones de a 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

Oracle no tiene nada equivalente a las columnas de identidad (al menos no lo hizo cuando lo utilicé por última vez). Esto podría ser relevante para SQL Server pero no necesitaría hacerlo en Oracle. – ConcernedOfTunbridgeWells

+0

En realidad, Oracle tiene un equivalente a las columnas de identidad: un disparador junto con una secuencia. Una base de datos bien diseñada usará esto. Visage puede necesitar usar mi 'Solución 3' en Oracle si no tiene acceso a la secuencia que genera el valor de ID. También en casos comunes, puede salir adelante con la "Solución 1" o la "Solución 2", que son más simples y más eficientes porque no crean una tabla temporal. Para situaciones en las que la secuencia * es * accesible, he agregado una 'Solución 4' que muestra cómo usarla para evitar la iteración. –

5

respuesta rápida

importación en una tabla de ensayo, pero los valores poblar asignada ID de la misma secuencia utilizado para producir valores de ID de la tabla de destino. Esto está garantizado para evitar conflictos entre valores de ID ya que el motor DBMS admite el acceso simultáneo a secuencias.

Con los valores de ID en el nodo mapeado (ver más abajo) la reasignación de los valores de ID para los bordes es trivial.

Respuesta larga

Se necesita un mecanismo que asigna los valores entre las viejas llaves de la fuente y nuevas claves en el destino. La forma de hacerlo es crear tablas de etapas intermedias que contengan las asignaciones entre los kays antiguos y nuevos.

En Oracle, las claves de autoincrementing generalmente se hacen con secuencias de la manera que usted ha descrito. Necesita construir tablas de etapas con un marcador de posición para la clave 'antigua' para que pueda hacer la reasignación. Use la misma secuencia que la utilizada por la aplicación para completar los valores de ID en las tablas de bases de datos de destino reales. El DBMS permite accesos concurrentes a secuencias y usa la misma secuencia de garantías para que no se produzcan colisiones en los valores de ID mapeados.

Si usted tiene un esquema como:

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

Esto le permitirá importar en la tabla STAGE_NODE, la preservación de los valores clave importados. El proceso de inserción coloca el ID original de la tabla importada en STAGED_ID y rellena el ID de la secuencia.

Asegúrese de que utiliza la misma secuencia que es utilizado para rellenar la columna ID en la tabla de destino. Esto asegura que no tendrá colisiones de teclas cuando vaya al inserte en la tabla de destino final. Es importante reutilizar la misma secuencia.

Como un efecto secundario útil, esto también permitirá que la importación se ejecute mientras se llevan a cabo otras operaciones en la mesa; las lecturas concurrentes en una sola secuencia están bien. Si es necesario, puede ejecutar este tipo de proceso de importación sin cerrar la aplicación.

Una vez que tenga esta asignación en la tabla de ensayo, los valores de ID de la tabla EDGE son triviales para calcular con una consulta como:

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 

Los valores EDGE asignadas se pueden rellenar de nuevo en las tablas de importación utilizando una consulta de ACTUALIZACIÓN con una combinación similar o insertada directamente en la tabla de destino a partir de una consulta similar a la anterior.

0

Solía ​​hacer este tipo de cosas mucho, pero mi memoria es un poco confusa. Te daré la idea general, espero que pueda apuntar en la dirección correcta.

Básicamente, solo puede hacer esto si tiene una segunda columna confiable 'clave única' en la tabla 'principal'. Si no, deberás crear uno.

Digamos que tenemos estas tablas

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

HIERARCHY[idparent, idchild] 

Lo que se quiere hacer es primeros elementos de copia de SourceDB a TargetDB, dejando TargetDB crear sus propios valores para la columna id.

Luego hay que copiar la jerarquía desde SourceDB a TargetDB, pero hay que hacer un ensamblar al igual que para obtener el nuevo id:

y hay que hacer lo mismo para la columna de la idchild.

Esto le dará algo como esto (sintaxis no probado, y oxidado, y probablemente 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 

estoy presumiendo que estas dos bases de datos están 'conectados' suficiente que usted puede hacer consultas entre bases de datos . Si tiene que serializar en un archivo, se vuelve un poco más ... complicado.

0

Puede lograr lo que necesita con una tabla temporal en la base de datos de destino. Dado que los ID se generan automáticamente, el siguiente código no generará ninguna colisión.

Voy a suponer que la base de datos de origen se llama SourceDb y la base de datos de destino se llama TargetDb. También voy a ASSUM esta estructura de la tabla:
Términos: ID, texto
Relaciones: parentid, childid

Crear una tabla temporal en TargetDB con este estructura:
TempTerms: OldId, texto, OldParentId, NewID, NewParentId

El siguiente código copiar su subárbol a la base de datos de destino.

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

¿Qué sucede al pasar los datos como XML? Está diseñado naturalmente para trabajar con estructuras de árbol, y muchos DBMS incluyen un buen soporte para el análisis y la conversión de XML.

Deberá asegurarse de que el nodo X en DB1 se correlaciona con el nodo Y en DB 2, pero para eso debe usar algún dato sobre el nodo (un nombre, etc.) más allá de la clave principal.

También puede compensar las claves de cada DB en una cantidad regular (digamos 2^32) y usar una tecla BIG INTEGER. Limita las entradas a 2^32, pero sigue siendo útil.

(I puede no entender la pregunta aquí, pero espero que no.)

No
Cuestiones relacionadas