0

我编写了一个 Oracle 函数(用于 8i)来获取受 DML 语句影响的行,模拟来自 PostgreSQL 的 RETURNING * 的行为。典型的函数调用如下所示:

SELECT tablename_dml('UPDATE tablename SET foo = ''bar''') FROM dual;

该函数是为每个表自动创建的,并使用动态 SQL 执行作为参数传递的查询。此外,动态执行查询的语句也包含在 BEGIN .. END 块中:

EXECUTE IMMEDIATE 'BEGIN'||query||' RETURNING col1, col2 BULK COLLECT INTO :1, :2;END;' USING OUT col1_t, col2_t;

这种特殊构造背后的原因是,它似乎是从影响多行的 DML 语句中获取值的唯一方法。col1_t 和 col2_t 都被声明为与表列对应的类型的集合。

最后,到问题。当传递的查询包含子选择时,函数的执行会产生语法错误。下面是一个简单的例子来说明这一点:

CREATE TABLE xy(id number, name varchar2(80));
CREATE OR REPLACE FUNCTION xy_fn(query VARCHAR2) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'BEGIN '||query||'; END;';
ROLLBACK;
RETURN 5;
END;
SELECT xy_fn('update xy set id = id + (SELECT min(id) FROM xy)') FROM DUAL;

最后一条语句产生以下错误:(那里提到的 SELECT 是 SELECT min(id))

ORA-06550:第 1 行,第 32 列:PLS-00103:在预期以下情况之一时遇到符号“SELECT”:( - + mod not null others avg count current exists max min prior sql stddev sum variance execute forall time timestamp interval日期

此问题出现在 8i (8.1.6) 上,但不是 10g。如果 BEGIN .. END 块被删除 - 问题就消失了。如果查询中的子选择被其他东西(即常量)替换,问题就消失了。

不幸的是,我坚持使用 8i 并删除 BEGIN .. END 不是一个选项(参见上面的解释)。

这里有特定的 Oracle 8i 限制吗?是否可以使用动态 SQL 来克服它?

4

2 回答 2

2

不知道为什么你需要做所有这些工作。Oracle 8i 支持 RETURNING INTO 与批量收集。 了解更多

因此,您应该能够在非动态 SQL 中执行此语句。像这样的东西:

UPDATE tablename 
SET foo = 'bar' 
returning  col1, col2 bulk collect into col1_t, col2_t;
于 2012-04-02T16:09:57.247 回答
1

去掉所有无关紧要的东西,我认为你的问题很简单。

此更新语句在 SQL 中运行:

update xy set id = id + (SELECT min(id) FROM xy);

这个匿名块也运行:

begin
    update xy set id = id + 100;
end;

但是将两者结合起来是行不通的:

begin
    update xy set id = id + (SELECT min(id) FROM xy);
end;

可能您遇到了旧版 Oracle 的限制。在 9i 之前,SQL 引擎和 PL/SQL SQL 引擎总是不同步。因此,SQL 支持的最新特性在 PL/SQL 中通常不受支持。好像你有其中之一。

由于 9i Oracle 一直在努力使这两个引擎保持同步,因此很难找到在 SQL 中工作但在 PL/SQL 中不工作的东西。

鉴于您的任务性质,升级您的 Oracle 版本已经结束。所以我只能建议你有两个过程,一个支持子查询语法(通过避免需要这样的子查询。像这样:

CREATE OR REPLACE FUNCTION xy_sqfn
    (main_query VARCHAR2
      , sub_query VARCHAR2   ) 
    RETURN NUMBER 
IS      
    n pls_integer;
BEGIN         
    execute immediate sub_query into n;
    EXECUTE IMMEDIATE 'BEGIN '||main_query||'; END;'
          using n;
    RETURN 5; 
END;

像这样称呼它

result := xy_sqfn ('update xy set id = id + :1'
                   , 'SELECT min(id) FROM xy');

现在这种方法不适用于相关的子查询。因此,如果您拥有其中任何一个,您将需要再次做一些不同的事情。


顺便说一句,使用 AUTONOMOUS TRANSACTION 杂注来伪造在 SELECT 语句中执行 DML 是非常可怕的。为什么不直接在 PL/SQL 中运行函数呢?还是使用程序?我想您会说这没关系,因为您只是在编写一些笨拙的代码来支持数据迁移。这很公平,但为了未来寻求者的利益:不要这样做!这是非常糟糕的做法!

于 2012-04-03T12:02:42.903 回答