从 update 语句成功调用函数,但从 select 语句调用时出错

Posted

技术标签:

【中文标题】从 update 语句成功调用函数,但从 select 语句调用时出错【英文标题】:Function called successfully from update statement but gets error when called from select statement 【发布时间】:2015-05-10 14:14:54 【问题描述】:
CREATE TABLE plch_test
(
   x   NUMBER
 , y   VARCHAR2 (3)
);

BEGIN
   INSERT INTO plch_test
        VALUES (1, 'NO');

   INSERT INTO plch_test
        VALUES (2, NULL);

   COMMIT;
END;
/

CREATE OR REPLACE FUNCTION silly_function (p_x NUMBER)
   RETURN VARCHAR2
IS
   l_y   plch_test.y%TYPE;
BEGIN
   SELECT t.y
     INTO l_y
     FROM plch_test t
    WHERE t.x = silly_function.p_x;

   RETURN l_y;
END;

运行以下代码块后,我会在屏幕上看到什么?

BEGIN
UPDATE plch_test SET y = 'YES' WHERE silly_function (x) != 'YES';  -- Line 2 Function call
 select silly_function(x) from dual; -- Line 3
   DBMS_OUTPUT.put_line ('Updated=' || SQL%ROWCOUNT);
EXCEPTION
   WHEN VALUE_ERROR
   THEN
      DBMS_OUTPUT.put_line ('VALUE_ERROR');
   WHEN OTHERS
   THEN
      DBMS_OUTPUT.put_line ('OTHER_ERROR');
END;
/

我认为第 2 行和第 3 行会抛出错误 X: invalid identifier。但是,当我执行上面的脚本时,第 2 行执行并且第 3 行抛出了异常。谁能解释一下第 2 行和第 3 行的区别?

【问题讨论】:

【参考方案1】:

第 2 行:

UPDATE plch_test SET y = 'YES' WHERE silly_function (x) != 'YES';

... 将更新表中的一行,将y 设置为'NO' 的行。另一行为空,因此不等于或不等于任何内容。 x 是一个有效的标识符,因为您指的是表plch_test,它有一个具有该名称的列。为每一行调用该函数,该行的值为x

第 3 行:

 select silly_function(x) from dual;

... 会出错,因为您没有选择任何内容,当然您必须在 PL/SQL 上下文中执行此操作。但是你也没有 x 范围内的变量,所以它仍然会出错。

所以这不会出错(希望;未经测试):

DECLARE
  l_x plch_test.x%type;
  l_y plch_test.y%type;
BEGIN
  UPDATE ... ;
  l_x := 1;
  select silly_function(l_x) into l_y from dual;
END;

捕捉和压制你得到的实际异常并没有帮助自己,否则这将非常明显。

你也不需要在这里选择,因为你可以做一个作业:

  l_y := silly_function(l_x);

在您的 cmets 并通过测试进行验证后,您所说的更新不起作用,因为它会抛出 ORA-04091: table SCHEMA.PLCH_TEST is mutating, trigger/function may not see it。您的更新是根据y 的当前值决定给y 什么值。但是在更新语句中间的“当前值”是什么意思? Oracle 应该在函数内部的选择中使用 y 的哪个值 - 更新前或更新后的值?这个例外实际上是在说它不能安全且一致地选择,所以你要求它做一些不安全和不一致的事情,这是 RDBMS 真正试图避免的。

解决此问题的一种方法是将查询/函数调用与更新分开,例如带有光标的循环:

DECLARE
  CURSOR c IS
    SELECT * FROM plch_test
    WHERE silly_function (x) != 'YES'
    FOR UPDATE;
BEGIN
  --UPDATE plch_test SET y = 'YES' WHERE silly_function (x) != 'YES';
  FOR r IN c LOOP
    UPDATE plch_test SET y = 'YES' WHERE CURRENT OF c;
  END LOOP;
END;
/

anonymous block completed

SELECT * FROM plch_test;

         X Y 
---------- ---
         1 YES
         2    

【讨论】:

但是,显然第 2 行抛出 mutating error 并且不会更新任何行。感谢您指出在使用select 时使用INTO 子句。我使用了select silly_function(x) into y from dual; 并得到了同样的错误(无效的X 标识符)。我知道,可以做同样的事情,你在最后写的。但是,我只是想知道为什么从select 调用时调用的相同函数不起作用? @jWeaver - 由于您没有 into 子句,因此该函数无法从 select 中工作,而且您似乎仍然没有名为 x 的变量。这就是我定义和使用l_x 变量的原因。你为什么现在谈论变异表 - 你在触发器中得到这个问题,而不是你所展示的匿名块。 请执行上面的块。我已经执行了上面的块并看到了错误。 @jWeaver - 好的,我专注于选择。您说更新在问题中有效,但正是它导致了变异表错误。更新了解决方法,尽管这不是您最初询问的内容。 我的问题是,当我评论 line 2 然后 select silly_function(x) into y from dual; 抛出错误 X : invalid identifier 而注释掉 line 3 update 声明有效。当我说工作时,这意味着我没有收到invalid identifier 错误,而是出现了变异错误。这意味着函数是从update 语句调用的。正确的 ?然后问题出现了,为什么 update throws exception 而不是 invalid identifier error ?我们使用相同的参数调用相同的函数。

以上是关于从 update 语句成功调用函数,但从 select 语句调用时出错的主要内容,如果未能解决你的问题,请参考以下文章

避免在 UPDATE 语句中多次调用标量函数

PL/SQL自定义函数

从 MinGW 调用 MSVC DLL 函数

顺序执行

为啥 ssh 从 crontab 失败但从命令行执行时成功?

Web安全SQL注入[三]