pl/sql 块中的子选择上的 Oracle 8i 动态 SQL 错误

Posted

技术标签:

【中文标题】pl/sql 块中的子选择上的 Oracle 8i 动态 SQL 错误【英文标题】:Oracle 8i dynamic SQL error on subselects in pl/sql blocks 【发布时间】:2012-04-02 15:58:37 【问题描述】:

我编写了一个 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 其他 avg count 当前存在 max min prior sql stddev sum 方差执行forall时间时间戳间隔日期

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

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

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

【问题讨论】:

8i??延长支持已于 5 年前结束... @Ben:你可以笑,但我仍在对 8i 数据库实例进行更改... @JeffreyKemp,我没笑!我知道有时很难移动(我还有一些相互连接的 9i 盒子 - 但我们已经升级了它们)。如果真的应该使用受支持的版本……这比使用不受支持的版本更有意义。 @ben - 有问题的版本是 8.1.6,甚至不是 8i 的终端版本。所以无论如何都没有资格获得扩展支持。 我实际上正在将数据迁移到 PostgreSQL,但我仍然需要 8i 来保证转换的质量。 【参考方案1】:

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

所以您应该能够在非动态 SQL 中执行此语句。像这样的:

UPDATE tablename 
SET foo = 'bar' 
returning  col1, col2 bulk collect into col1_t, col2_t;

【讨论】:

我有很多针对同一张表的查询。我想避免创建匿名 pl/sql 块,将它们包装在一个可以为每个查询调用的过程中。鉴于此,我必须动态执行传递给此类过程的查询。【参考方案2】:

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

此更新语句在 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 中运行函数呢?还是使用程序?我想您会说这没关系,因为您只是在编写一些笨拙的代码来支持数据迁移。这很公平,但为了未来寻求者的利益:不要这样做!这是非常糟糕的做法!

【讨论】:

谢谢,我需要选择和自治事务的原因是来自 DML 语句的数据应该来自 python 应用程序,该应用程序将结果与来自 PostgreSQL 数据库的类似结果进行比较。考虑到将 DML 语句包装在函数中的方法的所有障碍以及 SQL 和 pl/sql 引擎之间的 8i 不一致问题,我将根据您之前的答案转移到匿名 pl/sql 块,使用主机变量来收集结果。祝我好运:-)

以上是关于pl/sql 块中的子选择上的 Oracle 8i 动态 SQL 错误的主要内容,如果未能解决你的问题,请参考以下文章

类似于 Oracle PL/SQL 块中的 finally Block (JAVA)

Oracle 匿名 PL/SQL 块中缺少关键字错误

从 oracle PL/SQL 查看变量的值

为啥在匿名 PL/SQL 块中没有立即引发异常?

从 PL/SQL 块中的 CASE 语句调用 Procs:面临的问题

从 pl/sql 异常块中“一次”关闭所有游标