DBMS_SQL.BIND_VAR 到一个函数(例如 sysdate)

Posted

技术标签:

【中文标题】DBMS_SQL.BIND_VAR 到一个函数(例如 sysdate)【英文标题】:DBMS_SQL.BIND_VAR to a function (e.g. sysdate) 【发布时间】:2016-10-07 08:14:36 【问题描述】:

我认为我的问题最好用一个例子来描述:

Declare
  example1 varchar2(300) := 'sysdate';
  example2  varchar2(300) := 'null';
  example3  varchar2(300) := 'user';
  example4  varchar2(300) := '''Just some Text''';

  cursor_name INTEGER;
  rows_processed INTEGER;
BEGIN

    cursor_name := dbms_sql.open_cursor;

    DBMS_SQL.PARSE(cursor_name, 'UPDATE table_name SET column = :x', DBMS_SQL.NATIVE);
    DBMS_SQL.BIND_VARIABLE(cursor_name, ':x', example1);

    rows_processed := DBMS_SQL.EXECUTE(cursor_name);
    DBMS_SQL.CLOSE_CURSOR(cursor_name);
end;
/

所有“exampleX”变量都将绑定为 varchar2 而不会被“翻译”。

我以前使用 Execute Immediate,但由于性能优化而不得不切换到 DBMS_SQL。使用立即执行当然没有问题,如果您使用这种方法:

Execute Immediate 'UPDATE table_name SET column = ' || example1;

但我想不出用 BIND_VARIABLE 来归档它的方法。

(当然,我可以像 Execute Immediate 一样在 PARSE 语句中连接变量,但我认为这样会降低性能。在这种情况下,性能非常重要)


编辑:

一个更接近现实的例子是:

将数据从一个 DB(SRC) 复制到另一个 DB(DEST),我有这个帮助表:

CREATE TABLE "DEST_TAB_COLUMNS" 
   (    "OWNER" VARCHAR2(30 BYTE), 
    "TABLE_NAME" VARCHAR2(30 BYTE), 
    "COLUMN_NAME" VARCHAR2(30 BYTE), 
    "DATA_TYPE" VARCHAR2(106 BYTE), 
    "OPERATION_TYPE" VARCHAR2(30 BYTE), 
    "OPERATION_FUNCTION" VARCHAR2(200 BYTE)
   ) ;

在此表中,我定义了我对 DEST 端感兴趣的列。 而且我可以选择定义一个“OPERATION_FUNCTION”来替换某个列值。

所以一个条目看起来像:

SRC_OWNER_NAME |样品表 | SAMPLE_COL | VARCHAR2 |更换 | '空'

SRC_OWNER_NAME |样品表 | SAMPLE_COL2 |日期 |更换 |系统日期

在 SRC 方面,我定义了我想要传输的数据。这是一个简单的表格,基本上看起来像:

CREATE TABLE "SRC_TRANSFER_DATA" 
   (    "OWNER" VARCHAR2(30 BYTE), 
    "TABLE_NAME" VARCHAR2(30 BYTE), 
    "WHERE_CLAUSE" VARCHAR2(300 BYTE), 
   ) ;

示例: 测试者 |样品表 | SPECIAL_COLUMN = 123

现在程序在 SRC_TRANSFER_DATA 上循环(在 DEST 上)并构造一个 MERGE 语句。为了做到这一点,它还会在 DEST_TAB_COLUMNS 表中查找该表和列的规则是否存在。 如果有规则,我将绑定变量添加到我的集合中:

l_hostvariable_map(':p'||l_hostvar_cnt) := r_col.operation_function;

最后,我将查看这个集合以进行绑定。 最终的 Merge(简而言之)可能如下所示:

MERGE INTO dest_table dest 
USING 
(SELECT table_column FROM src_table WHERE special_column= :p1) 
src ON 
(dest.special_column= :p2) 
WHEN matched 
THEN UPDATE SET 
dest.column1=src.column1,dest.column2= :p3,dest.column3= :p4
WHEN NOT matched 
THEN INSERT 
(dest.column1,dest.column2,dest.column3) 
VALUES 
(src.column1,:p5,:p6)

一些 :pX 是一个“函数”。就像编辑之前的例子一样。

我希望这会让它更清晰,而不是更复杂;)

【问题讨论】:

为了澄清:这里的示例变量实际上是表中的字段。这就是为什么它的“sysdate”作为字符串而不是 var := sysdate;因为该表包含应该为变量调用什么“函数”的信息。而且我必须使用 DBMS_SQL,因为我们有动态数量的绑定变量 我不明白,您能否提供更接近您的真实代码的“虚构”示例。 SYSDATEUSERNULL 肯定不是任何表的列名。 @WernfriedDomscheit 完成。希望对你有帮助 【参考方案1】:

查看BIND_VARIABLE documentation:

注意 BIND_VARIABLE 被重载以接受不同的数据类型。

所以,你的代码应该是这样的:

example1 DATE := SYSDATE;
example2  varchar2(300) := NULL;
example3  varchar2(30) := USER;
example4  varchar2(300) := 'Just some Text';

如果你使用Execute Immediate,最好使用

Execute Immediate 'UPDATE table_name SET column = :a' USING example1;

顺便说一句,在早期的 Oracle 版本(即 Oracle 10)中,使用 Execute ImmediateDBMS_SQL 包确实存在性能差异。通常DBMS_SQL 更快。但是,在当前版本中,当我比较它们时,我不再有任何性能差异。当然,只有在任何情况下都使用绑定变量,才能获得类似的性能。

另外请注意,使用绑定变量比静态代码快 99.9% - 尽可能使用它们。它在 SQL 注入和引用问题方面也很有用。

更新:

根据您的输入,您的程序可能如下所示:

Declare
  val_date date;
  var_varchar varchar2(3000);
  var_number number;

  cursor_name INTEGER;
  rows_processed INTEGER;
BEGIN

   for aCol in (select * from DEST_TAB_COLUMNS) loop   
      cursor_name := dbms_sql.open_cursor;
      DBMS_SQL.PARSE(cursor_name, 'UPDATE '||aCol.table_name||' SET '||aCol.COLUMN_NAME||' = :val', DBMS_SQL.NATIVE);

      if aCol.DATA_TYPE = 'DATE' then
          execute immediate 'begin :res := '||aCol.OPERATION_FUNCTION||'; end;' using out val_date;
          DBMS_SQL.BIND_VARIABLE(cursor_name, ':x', val_date);
      elsif aCol.DATA_TYPE = 'VARCHAR2' then
          execute immediate 'begin :res := '||aCol.OPERATION_FUNCTION||'; end;' using out val_varchar;
          DBMS_SQL.BIND_VARIABLE(cursor_name, ':x', val_varchar);
      elsif aCol.DATA_TYPE = 'NUMBER' then
          execute immediate 'begin :res := '||aCol.OPERATION_FUNCTION||'; end;' using out val_number;
          DBMS_SQL.BIND_VARIABLE(cursor_name, ':x', val_number);
      end if;
      rows_processed := DBMS_SQL.EXECUTE(cursor_name);
      DBMS_SQL.CLOSE_CURSOR(cursor_name);
   end loop;
end;
/

当然,上述过程会非常缓慢,因为您是逐列和逐行处理的。无论如何,我假设您了解您的代码的外观。您的函数不仅可以返回单个值,还可以返回 PL/SQL 表中的多个值。

【讨论】:

感谢您的回答。请阅读我添加到问题中的评论。【参考方案2】:

您的示例出错的地方是它尝试传递文本字符串“sysdate”而不是日期值。绑定变量用于传递值,而不是构造查询文本。

declare
    example1        date := sysdate;  -- an actual date value, not the word 'sysdate'!
    cursor_id       integer;
    rows_processed  integer;
begin
    cursor_id := dbms_sql.open_cursor;

    dbms_sql.parse(cursor_id, 'update demo set dt = :x', dbms_sql.native);
    dbms_sql.bind_variable(cursor_id, 'x', example1);
    rows_processed := dbms_sql.execute(cursor_id);

    dbms_sql.close_cursor(cursor_id);
end;

dbms_sql.bind_variable 的第二个参数是name,所以我通过'x' 而不是':x',尽管它似乎都接受。)

正如 Wernfried 已经指出的,您不需要 DBMS_SQL 的所有复杂性,因为您可以使用 execute immediate

declare
    example1 date := sysdate;
begin
    execute immediate 'update demo set dt = :x' using example1;
end;

【讨论】:

也谢谢你。但请阅读我添加到问题中的评论。看来我的例子太简单了:D【参考方案3】:

我不明白你想用函数替换一些 "binds"。它的功能应该在运行时执行,例如:

 execute immediate 'UPDATE TEST_TABLE 
    SET a = :bind_function'
  using 'substr(a,1,10)';

应该被执行为:

 UPDATE TEST_TABLE 
    SET a = substr(a,1,10);

Oracle 绑定变量是不允许的; 但是你可以编写你的函数来替换占位符

  declare 
    l_sql varchar2(4000); 
    l_placeholder1 varchar2(4000) := '(\w+)\s+/\*placeholder_1\*/';
    l_function1    varchar2(4000) := 'substr(\1,1,10)';
    l_placeholder2 varchar2(4000) := '(\w+)\s+/\*placeholder_2\*/';
    l_function2    varchar2(4000) := 'nvl(\1,1000)';
  begin
    l_sql :=  'UPDATE TEST_TABLE 
    SET column_a = column_a /*placeholder_1*/
      , column_b = column_b /*placeholder_2*/
      , column_c = column_e /*placeholder_1*/
    where column_d /*placeholder_2*/ = 10';
    l_sql := REGEXP_REPLACE(l_sql,l_placeholder1,l_function1); 
    l_sql := REGEXP_REPLACE(l_sql,l_placeholder2,l_function2); 
    dbms_output.put_line(l_sql);  
    execute immediate l_sql;
  end;

【讨论】:

以上是关于DBMS_SQL.BIND_VAR 到一个函数(例如 sysdate)的主要内容,如果未能解决你的问题,请参考以下文章

单例模式之C++实现

单例模式

单例模式

单例模式

(23)c#传智:单例模式,XML读写,委托,匿名函数,Lamda,多播委托

对return函数的认识