【问题标题】:How can I make an entire PL/SQL code block dynamic with bind variables?如何使用绑定变量使整个 PL/SQL 代码块动态化?
【发布时间】:2016-12-05 13:00:23
【问题描述】:

背景

我正在尝试创建一个可重用的 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;
/

【问题讨论】:

  • 标识符(表名、模式名等)无法绑定。
  • @NicholasKrasnov 谢谢!我想我只会验证输入变量。能否请您输入与答案相同的内容,以便我关闭它?

标签: oracle plsql dynamic-sql bind-variables


【解决方案1】:

由于多种原因(无法生成适当的执行计划、安全检查等),Oracle 不允许标识符绑定(表名、模式名、列名等)。因此,如果确实有必要,唯一的方法是在某种验证之后对这些标识符进行硬编码(以防止 SQL 注入)。

【讨论】:

    【解决方案2】:

    如果我理解得很好,您可以尝试一个技巧,在动态 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;
    

    【讨论】:

    • 努力+1。然而,目的被打败了,因为我们仍然必须在末尾连接一个字符串,这仍然让我们对 SQL 注入开放。
    • 为什么不直接vSQL := 'insert into tabDest select * from '||tableName;
    • @WernfriedDomscheit 见 tableName := 'MyTable;删除 MyTable';
    • execute immediate 不接受多个命令。无论如何,请检查我的答案并打包 DBMS_ASSERT。
    【解决方案3】:

    我认为你可以做得更简单。

    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 包提供了验证输入值属性的功能。

    【讨论】:

    • +1 表示 DBMS_ASSERT。我一次做 X 行的原因是为了避免大量撤消。这是为了移动非常大的审计表。
    猜你喜欢
    • 2011-04-06
    • 1970-01-01
    • 2011-12-10
    • 2014-07-12
    • 2013-11-07
    • 2014-07-18
    • 1970-01-01
    • 1970-01-01
    • 2013-07-10
    相关资源
    最近更新 更多