0

バックグラウンド

再利用可能な PL/SQL プロシージャを作成して、あるデータベースから別のデータベースにデータを移動しようとしています。

この目的のために、動的 SQL を使用しています。

プレースホルダーで REPLACE を使用すると、手順は完全に実行されます。ただし、セキュリティ上の理由から、バインド変数を使用したいと考えています。


質問

PL/SQLコード・ブロック全体を(バインド変数を使用して)動的にするにはどうすればよいですか? バインド変数の代わりに REPLACE を使用すると、正常に動作します。


複製方法

これをデータベースに複製するには、次のプロシージャをそのまま作成します。

create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
l_cursor_limit pls_integer := 500;
l_values_list varchar2(32767);

begin

select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
      table_name = i_table_name and
      virtual_column = 'NO';

l_sql := q'[
declare
l_cur_limit pls_integer := :l_cursor_limit;

cursor c_get_to_be_moved is
select :i_table_name.*, :i_table_name.rowid
from :i_table_name;

type tab_to_be_moved is table of c_get_to_be_moved%rowtype;

l_to_be_moved tab_to_be_moved;

begin  

open c_get_to_be_moved;
loop
    fetch c_get_to_be_moved
    bulk collect into l_to_be_moved limit l_cur_limit;
    exit when l_to_be_moved.count = 0;      

    for i in 1.. l_to_be_moved.count loop
        begin
            insert into :i_table_name@:i_destination values (:l_values_list);
        exception
        when others then
            dbms_output.put_line(sqlerrm);
            l_to_be_moved.delete(i);
        end;    
    end loop;
    forall i in 1.. l_to_be_moved.count
    delete
    from :i_table_name
    where rowid = l_to_be_moved(i).rowid;    

    for i in 1..l_to_be_moved.count loop
        if (sql%bulk_rowcount(i) = 0) then
            raise_application_error(-20001, 'Could not find ROWID to delete. Rolling back...');           
        end if;
    end loop;    
    commit;
end loop;          
close c_get_to_be_moved;

exception
when others then
    rollback;
    dbms_output.put_line(sqlerrm);
end;]';
execute immediate l_sql using l_cursor_limit, i_table_name, i_destination, l_values_list;
exception
when others then
    rollback;
    dbms_output.put_line(sqlerrm);
end;
/

そして、次の手順を実行できます。

begin
    move_data('MySchemaName', 'MyTableName', 'MyDatabaseLinkName');
end;
/
4

3 に答える 3

3

多くの理由 (適切な実行計画を生成できない、セキュリティ チェックなど) により、Oracle は識別子のバインド (テーブル名、スキーマ名、列名など) を許可していません。したがって、本当に必要な場合、唯一の方法は、何らかの検証を行った後にそれらの識別子をハードコーディングすることです (SQL インジェクションを防ぐため)。

于 2016-12-05T13:30:27.830 に答える
1

もっと簡単にできると思います。

create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);

begin



select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
      table_name = i_table_name and
      virtual_column = 'NO';

l_sql := 'insert into '||i_destination||'.'||i_table_name||' select * from '||i_schema_name||'.'||i_table_name;

execute immediate l_sql;

end;

SQL インジェクションが気になる場合は、パッケージDBMS_ASSERTを参照してください。この PL/SQL パッケージは、入力値のプロパティを検証する機能を提供します。

于 2016-12-05T14:26:31.547 に答える
1

私がよく理解していれば、動的 SQL 内で動的 SQL を使用するというトリックを試すことができます。

設定:

create table tab100 as select level l from dual connect by level <= 100;
create table tab200 as select level l from dual connect by level <= 200;
create table tabDest as select * from tab100 where 1 = 2;

これは機能しません:

create or replace procedure testBind (pTableName in varchar2) is
    vSQL varchar2(32000);
begin
    vSQL := 'insert into tabDest select * from :tableName';
    execute immediate vSQL using pTableName;
end;

しかし、これはトリックを行います:

create or replace procedure testBind2 (pTableName in varchar2) is
    vSQL varchar2(32000);
begin
    vSQL := q'[declare
                vTab  varchar2(30)    := :tableName;
                vSQL2 varchar2(32000) := 'insert into tabDest select * from ' || vTab;
               begin
                 execute immediate vSQL2;
               end;
              ]';
    execute immediate vSQL using pTableName;
end;
于 2016-12-05T13:36:04.423 に答える