【问题标题】:Extracting rows from a DB including depedent rows从数据库中提取行,包括相关行
【发布时间】:2009-06-22 15:54:02
【问题描述】:

我想为我的 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_id = 1 的 a 中提取行时,结果应该是该行和相关行的插入字符串:

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

我想这样做的原因是,我有一个包含许多不同表和约束的大型数据库,我想提取一小部分数据作为测试数据。

【问题讨论】:

    标签: sql database oracle testing export


    【解决方案1】:

    可能已经有一些工具可以做到这一点,但是从起始表中任意提取所有行表本身就是一项小型开发任务。我不能为你写完整的东西,但我可以让你开始 - 我开始写它,但大约 20 分钟后,我意识到我想承诺一个无偿的答案需要更多的工作。

    我可以看到它最好通过递归 PL/SQL 过程来完成,该过程将使用 dbms_ouput 和 user_cons_columns 和 user_constraints 为源表创建插入语句。您可以通过将所有插入写成好像列是 char 值来作弊,因为假设您的 NLS 参数在源系统和目标系统上相同,Oracle 会隐式地将任何 char 值转换为正确的数据类型。

    注意,如果你的表中有循环关系,下面的包会出现问题;此外,在早期版本的 Oracle 中,您可能会用 dbms_output 耗尽缓冲区空间。这两个问题都可以通过将生成的 sql 插入到 sql 上具有唯一索引的临时表中来解决,如果遇到唯一键冲突则中止递归。下面最大的节省时间是 MakeParamList 函数,它将返回列列表的游标转换为逗号分隔的列表,或单个表达式,当作为在针对表的查询中选择子句。

    还要注意,除非您进一步修改以下包,否则它不会真正起作用(我停止编写它的原因之一):生成的初始插入语句是基于传入的 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;
    

    【讨论】:

    • 感谢您的出色回答!我需要一些时间来解决这个问题,但我认为它包含了我自己获得所需解决方案所需的一切。
    【解决方案2】:

    我只是使用普通的旧 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;
    

    【讨论】:

    • 这假设 OP 知道所有从属表,我认为他不知道。我认为他正在寻找能够遍历依赖项并找到所有依赖表的东西。
    • 简单的情况下你是对的。不幸的是,我通常的情况比示例中的要复杂得多。当依赖链中涉及的表超过 10 个时,手动创建这些 select 是相当繁琐且容易出错的。
    • 您可以编写SQL从系统表中生成上述语句。请参阅我更长的回复。
    【解决方案3】:

    我认为 DBUnit 可以做到这一点。

    【讨论】:

      猜你喜欢
      • 2021-06-13
      • 1970-01-01
      • 1970-01-01
      • 2013-03-29
      • 1970-01-01
      • 2018-06-16
      • 1970-01-01
      • 2021-05-05
      • 1970-01-01
      相关资源
      最近更新 更多