在 oracle 中立即执行

Posted

技术标签:

【中文标题】在 oracle 中立即执行【英文标题】:Execute Immediate in oracle 【发布时间】:2016-04-21 12:53:55 【问题描述】:

我有以下查询,它在遇到符号时给出错误(在使用循环的行中。我正在尝试开发一个函数,它将动态参数作为 table_name、column_name、table_id 并用于其他表。

FUNCTION get_encryp_pass( table_name IN varchar2,column_name IN varchar2,table_id IN varchar2) RETURN VARCHAR2
  IS
  BEGIN
   EXECUTE IMMEDIATE 'for c1 in (select * from' || table_name ||) loop   
      EXECUTE IMMEDIATE 'update ' || table_name || ' set ' || column_name = encrypt_val(c1.column_name) || ' where ' || table_id || ' = ' || c1.table_id and column_name is not null;
      end loop;   
  END get_encrypt_pass;

【问题讨论】:

您没有足够的单引号。例如,在附加表名之后,您需要在 ) 之前再次加上引号。 EXECUTE IMMEDIATE 需要一个字符串。 @Nitesh 我试过但仍然遇到同样的错误。 Jchomel 你能解释一下吗? 样本表是什么样子的,该表的 3 个输入参数中有哪些可能的值? 但是您实际上并没有将加密密码作为字符串返回,是吗?您正在函数内部执行UPDATE 并修改表中的值。 UPDATE 无论如何都不能在 SQL 中完成。 假设您没有进行更新,而只是进行了 SELECT。想想你想要返回多少行。您要查找的是 1 个加密密码还是整个表?如果是整个表,那么你需要考虑返回数组。如果它只是 1 个密码,那么您需要传入一个 ID 的值来识别该 1 行并按它进行过滤。 【参考方案1】:

这应该可行:

CREATE PROCEDURE get_encryp_pass(table_name  IN varchar2,
                                 column_name IN varchar2,
                                 table_id    IN varchar2) IS
BEGIN
  EXECUTE IMMEDIATE 'begin for c1 in (select * from ' || table_name ||
                    ') loop update ' || table_name || ' set ' ||
                    column_name || ' = encrypt_val(c1.' || column_name ||
                    ') where ' || table_id || ' = c1.'||table_id||' and ' || column_name ||
                    ' is not null; end loop; end;'
    ;
END;

但是为什么不直接打电话给update FTP_SFTP_SERVER set PASSWORD=encrypt_val(PASSWORD) where PASSWORD is not null呢?

【讨论】:

这意味着table_id 不是您的专栏名称?在这种情况下,您必须传递另一个参数table_id_name 好的,我已经调整了程序。参数table_id 现在是主键的列名。在我的第一个版本中,它是 id 的值 :-) 我已更正:不再使用绑定变量 table_id【参考方案2】:

注意什么是变量,什么是字符串字面量,因此必须用单引号引起来……而字符串变量必须用双引号引起来:

EXECUTE IMMEDIATE 'update ' || table_name || ' set ' || column_name || ' = ''' || encrypt_val(c1.column_name) || ''' where ' || table_id || ' = ' || c1.table_id || ' and column_name is not null';

最佳做法是首先将语句连接到 varchar2 变量中并检查它。如果变量的内容语法正确且可执行,则 EXECUTE IMMEDIATE 应该也能正常工作

declare
   stmt varchar2(4000);
begin
   stmt := 'update ' || table_name || ' set ' || column_name || ' = ''' || encrypt_val(c1.column_name) || ''' where ' || table_id || ' = ' || c1.table_id || ' and column_name is not null';
   EXECUTE IMMEDIATE stmt;
end;

【讨论】:

a FOR - 循环不是一个简单的 SELECT/INSERT/UPDATE/DELETE 语句,因此您必须将所有代码包装在一个匿名块中,例如 "begin ....循环do_something;结束循环;end" 为什么要在 FOR 循环中为同一组行更新同一列? 一个简单的动态更新语句也能满足需求 您的代码看起来像是要将所有未加密的“column_name”转换为它们的加密等效项。如果是这样,意味着您更新所有行!,然后删除该 FOR 循环以及比较单个 ID 的 where 谓词......只需更新所有:update tbl1 set col1 = encrypt_val(col1) where col1 is not null【参考方案3】:

对于您的问题,我想我有另一种选择。在这种情况下可以使用 MERGE。请尝试一下,我没有工作区所以力求测试它,但它应该可以工作让我知道它是否有帮助。

FUNCTION get_encryp_pass(
    table_name  IN VARCHAR2,
    column_name IN VARCHAR2,
    table_id    IN VARCHAR2)
  RETURN VARCHAR2
IS
BEGIN
  EXECUTE IMMEDIATE 'MERGE INTO '||table_name||' t1 USING 
(
SELECT * FROM '||table_name|| ')t2
ON
(
t1.table_id = t2.table_id
)
WHEN MATCHED THEN
UPDATE SET t1.'||column_name||' = encrypt_val(t2.'||column_name||')'
||' WHERE and t1.'||column_name||' IS NOT NULL';
END;

【讨论】:

因此您传递的 table_name 不能有名为 table_id 的列 刚刚检查过它对我有用。 Y downvote plz justify

以上是关于在 oracle 中立即执行的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle 过程中立即执行

在 oracle 中立即执行

ORACLE 在立即执行中批处理 DDL 语句

Oracle SQL:在立即执行的内部循环中使用外部循环标识符

Oracle:立即执行和 ORA-01086 中的保存点

立即执行 Oracle