【问题标题】:Function called successfully from update statement but gets error when called from select statement从 update 语句成功调用函数,但从 select 语句调用时出错
【发布时间】: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 行的区别?

【问题讨论】:

    标签: oracle function plsql


    【解决方案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 ?我们使用相同的参数调用相同的函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-28
    • 1970-01-01
    • 1970-01-01
    • 2021-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多