Извлечение строк из БД, включая зависимые строки - PullRequest
4 голосов
/ 22 июня 2009

Я хотел бы сгенерировать строки вставки для строки в моей базе данных Oracle, включая все ее зависимые строки в других таблицах (и их зависимые строки).

Пример:

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

Когда я извлекаю строку из a с a_id = 1, результатом должна быть строка вставки для этой строки и зависимых строк:

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

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

Ответы [ 3 ]

12 голосов
/ 23 июня 2009

Может быть какой-то инструмент, который уже делает это, но произвольное извлечение всех таблиц строк из начальной таблицы само по себе является небольшой задачей разработки. Я не могу написать все для вас, но я могу начать с вас - я начал писать, но примерно через 20 минут я понял, что это немного больше работы, которую я хотел посвятить неоплачиваемому ответу.

Я вижу, что это лучше всего сделать с помощью рекурсивной процедуры PL / SQL, которая будет использовать dbms_ouput и user_cons_columns & user_constraints для создания оператора вставок для исходной таблицы. Вы можете немного обмануть, написав все вставки, как если бы столбцы были значениями char, поскольку Oracle неявно преобразует любые значения char в правильный тип данных, предполагая, что ваши параметры NLS идентичны в исходной и целевой системах.

Обратите внимание, что пакет ниже будет иметь проблемы, если у вас есть круговые отношения в ваших таблицах; также, в более ранних версиях Oracle вы можете исчерпать буферное пространство с помощью dbms_output. Обе проблемы можно решить, вставив сгенерированный sql в промежуточную таблицу с уникальным индексом sql и прервав рекурсию, если вы столкнулись с уникальным ключом. Значительная экономия времени ниже - это функция MakeParamList, которая преобразует курсор, который возвращает список столбцов, в список, разделенный запятыми, или в одно выражение, которое будет отображать значения этих столбцов в кавычках, разделенных запятыми, при запуске в качестве предложение select в запросе к таблице.

Обратите также внимание, что следующий пакет не будет работать до тех пор, пока вы не измените его (одна из причин, по которой я перестал его писать): генерируемый исходный оператор вставки основан на предположении, что переданный аргумент constraint_vals приведет к генерируется одна строка - конечно, это почти наверняка не так, как только вы начнете рекурсировать (так как у вас будет много дочерних строк для родителя). Вам нужно изменить генерацию первого оператора (и последующих рекурсивных вызовов), чтобы он был внутри цикла, чтобы обрабатывать случаи, когда вызов первого вызова EXECUTE IMMEDIATE генерирует несколько строк вместо одной. Здесь изложены основы того, как это работает, вам просто нужно разобраться в деталях и заставить работать внешний курсор.

Еще одно заключительное замечание: маловероятно, что вы могли бы запустить эту процедуру для генерации набора строк, которые при вставке в целевую систему приводили бы к «чистому» набору данных, так как, хотя вы получите все зависимые данные, эти данные могут зависеть от других таблиц, которые вы не импортировали (например, первая дочерняя таблица, с которой вы столкнулись, может иметь другие внешние ключи, которые указывают на таблицы, не связанные с вашей исходной таблицей). В этом случае вы можете начать с таблиц подробностей и двигаться вверх, а не вниз; сделав это, вы также захотите изменить порядок на сгенерированные вами операторы, используя утилиту сценариев или вставив sql в промежуточную таблицу, как я упоминал выше, с последовательностью, а затем выбрав ее по убыванию .

Что касается его вызова, вы передаете список столбцов, разделенных запятыми, чтобы ограничить их как constraint_cols, и соответствующий список значений, разделенных запятыми, как constraint_vals, например ::

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

Вот оно:

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 голос
/ 22 июня 2009

Я просто использую старый добрый SQL для выполнения этих задач - используйте операторы select для генерации ваших вставок:

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 голосов
/ 22 июня 2009

Я думаю, DBUnit может сделать это.

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