2009-06-22 5 views
4

Je souhaite générer des chaînes d'insertion pour une ligne de ma base de données Oracle, y compris toutes ses lignes dépendantes dans d'autres tables (et leurs lignes dépendantes).Extraction de lignes d'une base de données incluant des lignes de tolérance

Exemple:

CREATE TABLE a (
    a_id number PRIMARY KEY, 
    name varchar2(100) 
); 
CREATE TABLE b (
    b_id number PRIMARY KEY, 
    a_id number REFERENCES a(a_id) 
); 

Lorsque j'extraire la ligne à partir d'un avec a_id = 1, le résultat devrait être un insert-chaîne pour cette rangée et les rangées dépendantes:

INSERT INTO a(a_id, name) VALUES (1, 'foo'); 
INSERT INTO b(b_id, a_id) VALUES (1, 1); 
INSERT INTO b(b_id, a_id) VALUES (2, 1); 
INSERT INTO b(b_id, a_id) VALUES (3, 1); 

La raison pour laquelle Je veux faire ceci est, que j'ai la grande base de données avec beaucoup de différentes tables et contraintes entre alors et je voudrais extraire un petit sous-ensemble des données en tant que données de test.

Répondre

12

Il peut y avoir un certain outil qui le fait déjà, mais d'extraire arbitrairement toutes les tables lignes d'une La table de départ est une petite tâche de développement en soi. Je ne peux pas tout écrire pour vous, mais je peux vous aider à démarrer - j'ai commencé à l'écrire, mais après environ 20 minutes, j'ai réalisé que c'était un peu plus de travail que je voulais consacrer à une réponse non rémunérée.

Je peux voir que c'est mieux fait par une procédure PL/SQL récursive qui utiliserait dbms_ouput et user_cons_columns & user_constraints pour créer une instruction inserts pour la table source. Vous pouvez tricher un peu en écrivant toutes les insertions comme si les colonnes étaient des valeurs car, Oracle convertirait implicitement toutes les valeurs de caractères en le type de données correct, en supposant que vos paramètres NLS sont identiques sur le système cible source &. Notez que le paquet ci-dessous aura des problèmes si vous avez des relations circulaires dans vos tables; également, sur les versions antérieures d'Oracle, vous pouvez manquer d'espace tampon avec dbms_output. Les deux problèmes peuvent être résolus en insérant le sql généré dans une table de transfert qui a un index unique sur le SQL, et en abandonnant la récursivité si vous obtenez une collision de clé unique. La fonction MakeParamList est l'économiseur de temps le plus important. Elle convertit un curseur qui renvoie une liste de colonnes en une liste séparée par des virgules ou une seule expression qui affiche les valeurs de ces colonnes entre guillemets séparés par des virgules. select clause dans une requête sur la table. Notez également que le package suivant ne fonctionnera pas réellement tant que vous ne l'aurez pas modifié (l'une des raisons pour lesquelles j'ai arrêté de l'écrire): L'instruction d'insertion initiale générée est basée sur l'hypothèse que l'argument constraint_vals transmis entraînera: une seule ligne est générée - bien sûr, ce n'est certainement pas le cas une fois que vous commencez à récurer (puisque vous aurez beaucoup de lignes enfants pour un parent). Vous devrez modifier la génération de la première instruction (et les appels récursifs ultérieurs) pour être dans une boucle afin de gérer les cas où l'appel au premier appel IMMEDIATE EXECUTER génère plusieurs lignes au lieu d'une seule. Les bases pour le faire fonctionner sont ici, vous avez juste besoin de broyer les détails et de faire fonctionner le curseur externe. Une note finale également: Il est peu probable que vous puissiez exécuter cette procédure pour générer un ensemble de lignes qui, lorsqu'elles sont insérées dans un système cible, aboutiraient à un ensemble de données "propre", car bien que vous obteniez tous données dépendantes, ces données peuvent dépendre d'autres tables que vous n'avez pas importées (par exemple, la première table enfant que vous rencontrez peut avoir d'autres clés étrangères pointant vers des tables sans rapport avec votre table initiale).Dans ce cas, vous voudrez peut-être commencer par les tableaux de détail et travailler votre chemin au lieu de descendre; Pour ce faire, vous souhaiterez également inverser l'ordre des instructions que vous avez générées, soit en utilisant un utilitaire de script, soit en insérant le sql dans une table de transfert comme je l'ai mentionné plus haut, avec une séquence, puis en sélectionnant un tri décroissant. .

Quant à l'invocation, vous passez la virgule liste séparée par des colonnes pour entraveraussi constraint_cols et la virgule correspondante liste de valeurs séparées comme constraint_vals, par exemple:

exec Data_extractor.MakeInserts ('MYTABLE', 'COL1, COL2', '99, 105') 

Ici, il est:

CREATE OR REPLACE PACKAGE data_extractor 
IS 
    TYPE column_info IS RECORD(
     column_name user_tab_columns.column_name%TYPE 
    ); 

    TYPE column_info_cursor IS REF CURSOR 
     RETURN column_info; 

    FUNCTION makeparamlist(
     column_info column_info_cursor 
    , get_values NUMBER 
    ) 
     RETURN VARCHAR2; 

    PROCEDURE makeinserts(
     source_table  VARCHAR2 
    , constraint_cols VARCHAR2 
    , constraint_vals VARCHAR2 
    ); 
END data_extractor; 


CREATE OR REPLACE PACKAGE BODY data_extractor 
AS 
    FUNCTION makeparamlist(
     column_info column_info_cursor 
    , get_values NUMBER 
    ) 
     RETURN VARCHAR2 
    AS 
    BEGIN 
     DECLARE 
     column_name user_tab_columns.column_name%TYPE; 
     tempsql  VARCHAR2(4000); 
     separator  VARCHAR2(20); 
     BEGIN 
     IF get_values = 1 
     THEN 
      separator := ''''''''' || '; 
     ELSE 
      separator := ''; 
     END IF; 

     LOOP 
      FETCH column_info 
      INTO column_name; 

      EXIT WHEN column_info%NOTFOUND; 
      tempsql := tempsql || separator || column_name; 

      IF get_values = 1 
      THEN 
       separator := ' || '''''', '''''' || '; 
      ELSE 
       separator := ', '; 
      END IF; 
     END LOOP; 

     IF get_values = 1 
     THEN 
      tempsql := tempsql || ' || '''''''''; 
     END IF; 

     RETURN tempsql; 
     END; 
    END; 

    PROCEDURE makeinserts(
     source_table  VARCHAR2 
    , constraint_cols VARCHAR2 
    , constraint_vals VARCHAR2 
    ) 
    AS 
    BEGIN 
     DECLARE 
     basesql    VARCHAR2(4000); 
     extractsql   VARCHAR2(4000); 
     tempsql    VARCHAR2(4000); 
     valuelist    VARCHAR2(4000); 
     childconstraint_vals VARCHAR2(4000); 
     BEGIN 
     SELECT makeparamlist(CURSOR(SELECT column_name 
             FROM user_tab_columns 
             WHERE table_name = source_table), 0) 
      INTO tempsql 
      FROM DUAL; 

     basesql := 'INSERT INTO ' || source_table || '(' || tempsql || ') VALUES ('; 

     SELECT makeparamlist(CURSOR(SELECT column_name 
             FROM user_tab_columns 
             WHERE table_name = source_table), 1) 
      INTO tempsql 
      FROM DUAL; 

     extractsql := 'SELECT ' || tempsql || ' FROM ' || source_table 
         || ' WHERE (' || constraint_cols || ') = (SELECT ' 
         || constraint_vals || ' FROM DUAL)'; 

     EXECUTE IMMEDIATE extractsql 
         INTO valuelist; 

     -- This prints out the insert statement for the root row 
     DBMS_OUTPUT.put_line(basesql || valuelist || ');'); 

     -- Now we construct the constraint_vals parameter for subsequent calls: 
     SELECT makeparamlist(CURSOR( SELECT column_name 
             FROM user_cons_columns ucc 
              , user_constraints uc 
             WHERE uc.table_name = source_table 
              AND ucc.constraint_name = uc.constraint_name 
            ORDER BY position) 
          , 1) 
      INTO tempsql 
      FROM DUAL; 

     extractsql := 'SELECT ' || tempsql || ' FROM ' || source_table 
         || ' WHERE ' || constraint_cols || ' = ' || constraint_vals; 

     EXECUTE IMMEDIATE extractsql 
         INTO childconstraint_vals; 

     childconstraint_vals := childconstraint_vals; 

-- Now iterate over the dependent tables for this table 
-- Cursor on this statement: 
-- SELECT uc.table_name child_table, uc.constraint_name fk_name 
--  FROM user_constraints uc 
--   , user_constraints ucp 
--  WHERE ucp.table_name = source_table 
--  AND uc.r_constraint_name = ucp.constraint_name; 

     -- For each table in that statement, find the foreign key 
     -- columns that correspond to the rows 
     -- in the parent table 
     -- SELECT column_name 
     -- FROM user_cons_columns 
     -- WHERE constraint_name = fk_name 
     --ORDER BY POSITION; 

     -- Pass that columns into makeparamlist above to create 
     -- the constraint_cols argument of the call below: 

     -- makeinserts(child_table, ChildConstraint_cols, childconstrain_vals); 
     END; 
    END; 
END data_extractor; 
+1

Merci pour cette excellente réponse! J'aurai besoin de temps pour y arriver, mais je pense qu'il contient tout ce dont j'ai besoin pour arriver à la solution désirée par moi-même. –

+2

+1 pour tous les efforts, si rien d'autre! – DCookie

0

Je pense que DBUnit peut le faire.

1

je viens d'utiliser le bon vieux SQL pour effectuer ces tâches - utiliser les instructions select pour générer vos inserts:

set pagesize 0 
set verify off 

    SELECT 'INSERT INTO a(a_id, name) VALUES (' 
     || a_id || ', ' 
     || '''' || name || ''');' 
    FROM a 
    WHERE a_id = &&1; 

    SELECT 'INSERT INTO b(b_id, a_id) VALUES (' 
     || b_id || ', ' 
     || a_id || ');' 
    FROM b 
    WHERE a_id = &&1; 
+0

Cela suppose que l'OP connaisse toutes les tables dépendantes, ce que je ne pense pas. Je pense qu'il cherche quelque chose qui marchera sur les dépendances et trouvera toutes les tables dépendantes. – Joe

+0

Vous avez raison pour les cas simples. Malheureusement, mon cas habituel est beaucoup plus compliqué que dans l'exemple. Lorsque plus de 10 tables sont impliquées dans la chaîne de dépendance, la création manuelle de ces sélections est assez fastidieuse et sujette aux erreurs. –

+0

Vous pouvez écrire SQL pour générer les instructions ci-dessus à partir des tables système. Voir ma réponse plus longue. –

Questions connexes