动态生成给定表/列值的insert语句

时间:2017-09-11 18:11:02

标签: sql oracle plsql

我需要编写一个过程,它应该动态地返回任何给定表的插入脚本。以下是该程序的签名。 P_export_data_dml(table_name IN,column_name IN,column_value IN,data_dml OUT)

例: 表T1

    C1      |    C2  
----------  |   ----------
    10      |    20
    10      |    21

表T2

    C1      |    C2   |    C3
  -------   | ------- | -------- 
    10      |    20   |    30
    10      |    21   |    31

case1

Input:
P_export_data_dml(T1, C1, 10, data_dml)

Expected output:
Insert into T1(C1, C2) values(10,20);
Insert into T1(C1, C2) values(10,21);

情形2

Input:
P_export_data_dml(T2, C1, 10, data_dml)

Expected output:
Insert into T2(C1, C2, C3) values(10, 20, 30);
Insert into T2(C1, C2, C3) values(10, 21, 31);

当表中只有一条记录用于给定输入时,我能够生成一个insert语句。 (使用all_tab_columns然后为每个列提取给定的表以形成Insert语句的一部分,然后最终连接所有字符串以形成最终的INSERT语句。)

但是当有多个记录时,我面临着形成插入语句的挑战。你能帮我解决一下这些记录的逻辑,并为所有记录形成INSERT语句。

2 个答案:

答案 0 :(得分:1)

您的问题不清楚您计划用于data_dml OUT的数据类型。
这可能是一个集合,或一个clob等。

请注意,大​​多数支持Oracle的编辑器,如SQL Developer,Intellij IDEA,TOAD等已经内置了这种东西,具有强大的实现来转换结果集进入INSERTS

话虽如此,使用Dynamic SQL可以实现这种功能。

以下是一些轻量级示例,这些示例建立在您计划仅使用NUMBER用于column_value参数的前提下。可以根据需要为其他人或ANYDATA添加重载。

示例1:此示例将在INSERT中返回多个CLOB语句(可以使用集合返回类型)。它只会为INSERT NUMBER列生成DATA_TYPE秒。注意这里没有错误处理。

CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
  P_COLUMN_LIST       CLOB;
  V_VALUES_LIST       CLOB;
  TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
  V_USER_TAB_COLS     USER_TAB_COL_TABLE;
  V_SELECTER_SQL_TEXT CLOB := '';

  BEGIN

    SELECT *
    BULK COLLECT INTO V_USER_TAB_COLS
    FROM USER_TAB_COLUMNS
    WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
          AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER');

    P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;
    V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;

    FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
    LOOP
      P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
      V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
    END LOOP;

    V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
    EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;

  END;
/

然后尝试T1/C1

DECLARE
  V_RESULT CLOB;
BEGIN
  P_EXPORT_DATA_DML('T1','C1',10,V_RESULT);
  DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/

INSERT INTO T1 (C1,C2) VALUES ( 10,20);
INSERT INTO T1 (C1,C2) VALUES ( 10,21);
PL/SQL procedure successfully completed.

T2/C1

DECLARE
  V_RESULT CLOB;
BEGIN
  P_EXPORT_DATA_DML('T2','C1',10,V_RESULT);
  DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/

INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,21,31);
PL/SQL procedure successfully completed.

T2/C2

...
P_EXPORT_DATA_DML('T2','C2',20,V_RESULT);
...
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
PL/SQL procedure successfully completed.

要支持其他DATA_TYPE,您需要处理DATE/TIMESTAMP -> CHAR次转化,引用CHAR等。

以下是支持NUMBER + VARCHAR2

的示例
CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
  P_COLUMN_LIST       CLOB;
  V_VALUES_LIST       CLOB;
  TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
  V_USER_TAB_COLS     USER_TAB_COL_TABLE;
  V_SELECTER_SQL_TEXT CLOB := '';

  BEGIN

    SELECT *
    BULK COLLECT INTO V_USER_TAB_COLS
    FROM USER_TAB_COLUMNS
    WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
          AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER', 'VARCHAR2');

    P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;

    CASE WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'NUMBER'
      THEN
        V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;
      WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'VARCHAR2'
      THEN
        V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!''''||!' || V_USER_TAB_COLS(1).COLUMN_NAME || Q'!||''''!';
    END CASE;

    FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
    LOOP
      P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
      CASE WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'NUMBER'
        THEN
          V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
        WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'VARCHAR2'
        THEN
          V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || Q'!''''||!' || V_USER_TAB_COLS(POINTER).COLUMN_NAME || Q'!||''''!';
      END CASE;

    END LOOP;

    V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
    EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;

  END;
/

使用VARCHAR2表格进行测试:

CREATE TABLE T3 (C1 VARCHAR2(64), C2 NUMBER, C3 VARCHAR2(64));
INSERT INTO T3 VALUES ('XX',10,'AA');
INSERT INTO T3 VALUES ('XQ',10,'AQ');
INSERT INTO T3 VALUES ('XX',20,'AA');

获取多行:

DECLARE
  V_RESULT CLOB;
BEGIN
  P_EXPORT_DATA_DML('T3','C2',10,V_RESULT);
  DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/

INSERT INTO T3 (C1,C2,C3) VALUES ( 'XQ',10,'AQ');
INSERT INTO T3 (C1,C2,C3) VALUES ( 'XX',10,'AA');
PL/SQL procedure successfully completed.

答案 1 :(得分:1)

尝试此程序。请注意,它适用于NUMBER,VARCHAR,DATE(具有默认格式时)数据类型

set serveroutput on
create or replace
procedure p_export_data_dml(p_table in varchar2, p_filter_column in varchar2, p_filter_value in varchar2, p_dmls in out varchar2)
is
  cursor c_statements(p_table varchar2, p_filter_column varchar2, p_filter_value varchar2) is
    select 'select ''insert into '||p_table||' ('||
        listagg(column_name, ', ') within group (order by column_id) ||') values(''''''||'||
        listagg(column_name, '||'''''', ''''''||') within group (order by column_id)||'||'''''');'' from '||p_table||' where '||p_filter_column||' = '''||p_filter_value||'''' insert_statement
    from user_tab_columns
    where table_name = upper(p_table);

  v_output varchar2(4000);
  v_sql varchar2(4000);
  type t_cursor is ref cursor;
  c_cur t_cursor;
begin
  for r_statements in c_statements(p_table, p_filter_column, p_filter_value) loop
      v_sql := r_statements.insert_statement;
      dbms_output.put_line(v_sql);
      open c_cur for v_sql;
      loop
        fetch c_cur into v_output;
        exit when c_cur%notfound;
        if p_dmls = null then
          p_dmls := v_output;
        else
          p_dmls := p_dmls || '
        '||v_output;
        end if;
      end loop;
      close c_cur;
  end loop;
end;
/

然后您可以尝试使用以下

执行
declare
  v_text varchar2(32000);
begin
  p_export_data_dml('t1', 'c1', 10, v_text);
  dbms_output.put_line(v_text);
end;
/

输出可以是这样的:

    insert into t1 (C1, C2, C3, C4) values('10', '1', '11', '12-SEP-2017 07:54:38');
    insert into t1 (C1, C2, C3, C4) values('10', '2', '12', '12-SEP-2017 07:54:38');
    insert into t1 (C1, C2, C3, C4) values('10', '3', '13', '12-SEP-2017 07:54:38');