【问题标题】:PL/SQL Dynamic SQL USING clausePL/SQL 动态 SQL USING 子句
【发布时间】:2016-07-29 19:41:58
【问题描述】:

我正在使用 Oracle 11g 数据库,发布 11.2.0.3.0 - 64 位生产

我有几个已定义的包、过程、函数和数据类型。在大量使用集合、数组和其他数据结构完成大量中间计算之后,我最终需要动态创建一个数据库表来输出我的最终结果。对于这个问题,我有以下几点:

TYPE ids_t IS TABLE OF NUMBER INDEX BY PLS_INTEGER;

benefit_ids ids_t;

--Lots of other code which successfully populates benefit_ids. 
--benefit_ids has several million rows, and is used successfully as 
  the input to the following function:

FUNCTION find_max_ids(in_ids in ids_t)
RETURN ids_t
IS
    str_sql varchar2(200);
    return_ids ids_t;
BEGIN
    str_sql := 'SELECT max(b.benefit_id)
                FROM TABLE(:1) a
                JOIN benefits b ON b.benefit_id = a.column_value
                GROUP BY b.benefit_id';

    EXECUTE IMMEDIATE str_sql BULK COLLECT INTO return_ids USING in_ids;

    RETURN return_ids;
END;

上述工作正常,清楚地表明可以将数组作为参数传递给动态 sql 函数或过程。

但是,当我尝试使用 EXECUTE IMMEDIATE 和 USING 创建一个数据库表作为我的最终输出时,我遇到了问题:

PROCEDURE create_output_table(in_ids in ids_t, in_tbl_nme in varchar2)
AUTHID CURRENT_USER
IS
   str_sql := 'CREATE TABLE Final_Results AS (
                 SELECT a.client_id, a.benefit_id
                 FROM ' || in_tbl_nme || ' a
                 LEFT JOIN TABLE(:1) b on b.column_value = a.benefit_id
                 WHERE b.column_value is NOT NULL)';

   EXECUTE IMMEDIATE str_sql USING IN in_ids;

END;

我收到的唯一错误消息是 ORA-00933:SQL 命令未正确结束。但是,我看不出语法本身有什么问题,尽管我怀疑问题在于我如何在这种情况下应用 EXECUTE IMMEDIATE。

如有任何建议,我们将不胜感激。

【问题讨论】:

  • 我看不到您已声明 str_sql 并且缺少 BEGIN 关键字。尝试将这些内容合并到您的代码中。

标签: oracle plsql dynamic-sql


【解决方案1】:

您显示的代码没有得到 ORA-00933,但仍然无效:

create type ids_t is table of number
/
create table test_table (client_id number, benefit_id number)
/
insert into test_table values (1, 1)
/

declare
  str_sql varchar2(4000);
  in_tbl_nme varchar2(30) := 'TEST_TABLE';
  in_ids ids_t := ids_t(1, 2, 3);
begin
   str_sql := 'CREATE TABLE Final_Results AS (
                 SELECT a.client_id, a.benefit_id
                 FROM ' || in_tbl_nme || ' a
                 LEFT JOIN TABLE(:1) b on b.column_value = a.benefit_id
                 WHERE b.column_value is NOT NULL)';

   EXECUTE IMMEDIATE str_sql USING IN in_ids;
end;
/

Error report -
ORA-22905: cannot access rows from a non-nested table item

那个错误看起来不对;让我们投射它,看看它是否更快乐,即使它不应该是必要的:

declare
  str_sql varchar2(4000);
  in_tbl_nme varchar2(30) := 'TEST_TABLE';
  in_ids ids_t := ids_t(1, 2, 3);
begin
   str_sql := 'CREATE TABLE Final_Results AS (
                 SELECT a.client_id, a.benefit_id
                 FROM ' || in_tbl_nme || ' a
                 LEFT JOIN TABLE(CAST(:1 AS ids_t)) b on b.column_value = a.benefit_id
                 WHERE b.column_value is NOT NULL)';

   EXECUTE IMMEDIATE str_sql USING IN in_ids;
end;
/

Error report -
ORA-01027: bind variables not allowed for data definition operations

那个错误is described in this article

所以你需要分两步创建和填充表格:

declare
  str_sql varchar2(4000);
  in_tbl_nme varchar2(30) := 'TEST_TABLE';
  in_ids ids_t := ids_t(1, 2, 3);
begin
   str_sql := 'CREATE TABLE Final_Results AS
                 SELECT a.client_id, a.benefit_id
                 FROM ' || in_tbl_nme || ' a
                 WHERE 1=0'; -- or anything that always evaluates to false

   EXECUTE IMMEDIATE str_sql;

   str_sql := 'INSERT INTO Final_Results (client_id, benefit_id)
                 SELECT a.client_id, a.benefit_id
                 FROM ' || in_tbl_nme || ' a
                 LEFT JOIN TABLE(CAST(:1 AS ids_t)) b on b.column_value = a.benefit_id
                 WHERE b.column_value is NOT NULL';

   EXECUTE IMMEDIATE str_sql USING IN in_ids;
end;
/

PL/SQL procedure successfully completed.

select * from final_results;

 CLIENT_ID BENEFIT_ID
---------- ----------
         1          1

动态创建表通常不是一个好主意。除了模式管理和可维护性考虑之外,您必须确保只有一个会话正在调用该过程并且该表不存在。如果您有一个执行此工作的进程,使用结果然后删除表,那么您仍然必须确保它不能同时运行,并且如果中途失败可以重新启动。

如果所有工作都在同一个会话中完成,那么您可以创建一个(永久)全局临时表,作为一次性模式设置任务。填充它的插入仍然必须是动态的,因为 in_table_nme 未知,但这将是一个改进。 (我不确定为什么你在find_max_ids 中的查询是动态的,除非你也在动态创建benefits)。或者根据所涉及的数据量,您可以使用其他集合类型,而不是表。

GTT 中的数据仅对该会话可见,并在会话结束时被销毁。如果这不合适,那么可以创建一次普通表,这比动态创建/删除它要好。在这种情况下,您仍然需要防止多个会话同时运行该进程,因为它们可能看不到预期的数据。

【讨论】:

  • 感谢您的分析和 cmets。关于 ORA-00933 错误,原来我的原始代码中还有另一个未知错误导致了该错误。当我发布我的问题时,我没有重现该错误。当我更正我的原始代码时,您是正确的,因为真正的错误是 ORA-01027:不允许绑定变量。您引用的文章解释了这种情况 - 谢谢。我已经采用了您建议的代码并根据自己的目的对其进行了相应的修改,并且效果很好。我注意到 cmets 正在动态创建表,我通常同意,但这对我来说不是问题。
  • 我同意 Alex 的观点,即动态创建这些表可能是个坏主意。也许最好有一个表,其中包含源表名称的列(包含 in_ids 列表中的值)和其他两列?
猜你喜欢
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 2010-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-24
相关资源
最近更新 更多