【问题标题】:How to use session-global variables of type collection in oracle如何在 oracle 中使用集合类型的会话全局变量
【发布时间】:2014-04-24 01:39:00
【问题描述】:

我有一个包,它声明了一些数据库表的 %rowtype 类型表的集合。它还声明了一个用一些数据填充包级变量的函数。我现在可以用 dbms_output 打印数据,看起来不错。

但是当我在一些 sql 中使用包级变量时,我得到以下错误:

ORA-21700: object does not exist or is marked for delete
ORA-06512: at "TESTDB.SESSIONGLOBALS", line 17
ORA-06512: at line 5

这是我的代码:

创建一些虚拟数据:

drop table "TESTDATA";
/
CREATE TABLE "TESTDATA" 
(   "ID" NUMBER NOT NULL ENABLE, 
    "NAME" VARCHAR2(20 BYTE), 
    "STATUS" VARCHAR2(20 BYTE)
);
/   
insert into "TESTDATA" (id, name, status) values (1, 'Hans Wurst', 'J');
insert into "TESTDATA" (id, name, status) values (2, 'Hans-Werner', 'N');
insert into "TESTDATA" (id, name, status) values (3, 'Hildegard v. Bingen', 'J');
/

现在创建包:

CREATE OR REPLACE 
PACKAGE SESSIONGLOBALS AS 
  type t_testdata is table of testdata%rowtype;
  v_data t_testdata := t_testdata();
  function load_testdata return t_testdata;

END SESSIONGLOBALS;

和包体:

CREATE OR REPLACE
PACKAGE BODY SESSIONGLOBALS AS
  function load_testdata return t_testdata AS
    v_sql varchar2(500);
  BEGIN
    if SESSIONGLOBALS.v_data.count = 0 
    then
      v_sql := 'select * from testdata';
      execute immediate v_sql 
      bulk collect into SESSIONGLOBALS.v_data;

      dbms_output.put_line('data count:');
      dbms_output.put_line(SESSIONGLOBALS.v_data.count);

    end if; -- SESSIONGLOBALS.v_data.count = 0 

    -- ******************************
    -- this line throws the error
    insert into testdata select * from table(SESSIONGLOBALS.v_data);
    -- ******************************

    return SESSIONGLOBALS.v_data;
  END load_testdata;

END SESSIONGLOBALS;

执行示例:

DECLARE
  v_Return SESSIONGLOBALS.T_TESTDATA;
BEGIN
    v_Return := SESSIONGLOBALS.LOAD_TESTDATA();
    dbms_output.put_line('data count (direct access):');
    dbms_output.put_line(SESSIONGLOBALS.v_data.count);
    dbms_output.put_line('data count (return value of function):');
    dbms_output.put_line(v_Return.count);
END;

如果上面标记的行被注释掉,我会得到预期的结果。

那么谁能告诉我为什么会出现上述异常?

顺便说一句:对于我来说,绝对需要执行将数据填充为动态 sql 的语句,因为表名在编译时是未知的。 (v_sql := 'select * from testdata';)

【问题讨论】:

    标签: oracle collections plsql package scope


    【解决方案1】:

    解决方案是在包中使用流水线函数

    请参阅:http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm#CHDJEGHC(=> PL/SQL 表函数之间的管道 部分可以解决问题)。

    我的包现在看起来像这样(请从我的问题中获取表格脚本):

    create or replace 
    PACKAGE SESSIONGLOBALS AS 
      v_force_refresh boolean;
      function set_force_refresh return boolean;
    
      type t_testdata is table of testdata%rowtype;
      v_data t_testdata;
      function load_testdata return t_testdata;
      function get_testdata return t_testdata pipelined;
    END SESSIONGLOBALS;
    /
    
    create or replace 
    PACKAGE BODY SESSIONGLOBALS AS
      function set_force_refresh return boolean as
      begin
        SESSIONGLOBALS.v_force_refresh := true;
        return true;
      end set_force_refresh;
    
      function load_testdata return t_testdata AS
        v_sql varchar2(500);
        v_i number(10);
      BEGIN
        if SESSIONGLOBALS.v_data is null then
          SESSIONGLOBALS.v_data := SESSIONGLOBALS.t_testdata();
        end if;
    
        if SESSIONGLOBALS.v_force_refresh = true then
          SESSIONGLOBALS.v_data.delete;
        end if;
    
        if SESSIONGLOBALS.v_data.count = 0 
        then
          v_sql := 'select * from testdata';
          execute immediate v_sql 
          bulk collect into SESSIONGLOBALS.v_data;
    
        end if; -- SESSIONGLOBALS.v_data.count = 0 
    
        return SESSIONGLOBALS.v_data;
      END load_testdata;
    
      function get_testdata return t_testdata pipelined AS
        v_local_data SESSIONGLOBALS.t_testdata := SESSIONGLOBALS.load_testdata();
      begin
        if v_local_data.count > 0 then
          for i in v_local_data.first .. v_local_data.last
          loop
            pipe row(v_local_data(i));
          end loop;
        end if;
      end get_testdata;
    
    END SESSIONGLOBALS;
    /
    

    现在我可以像这样在 sql 中进行选择:

    select * from table(SESSIONGLOBALS.get_testdata());
    

    而且我的数据集只填充一次。 然而,它与简单的 从测试数据中选择 *; 从性能的角度来看,但我会在一些更复杂的用例中尝试这个概念。目标是避免执行一些非常庞大的 select 语句,这些语句涉及分布在多个架构中的大量表(架构的复数形式......?)。

    【讨论】:

      【解决方案2】:

      你使用的语法不起作用:

      insert into testdata select * from table(SESSIONGLOBALS.v_data); -- does not work
      

      你必须使用类似的东西:

      forall i in 1..v_data.count
        INSERT INTO testdata VALUES (SESSIONGLOBALS.v_data(i).id,
                                     SESSIONGLOBALS.v_data(i).name,
                                     SESSIONGLOBALS.v_data(i).status);
      

      (实际上复制了表中的行)

      【讨论】:

      • 感谢您的编辑...但我想表明 SESSIONGLOBALS 可以省略(但这仍然可以从1..v_data.count 看到)
      • 我通常保留包级变量(函数/过程)的包名,以可视化变量范围
      【解决方案3】:

      包级别的类型不能在 SQL 中使用。即使从包中调用您的 SQL,它仍然看不到该包的类型。

      我不确定您是如何得到该错误消息的,当我编译包时出现此错误,这很好地提示了问题:

      PLS-00642: local collection types not allowed in SQL statements
      

      要解决此问题,请创建一个类型和该类型的嵌套表:

      create or replace type t_testdata_rec is object
      (
          "ID" NUMBER, 
          "NAME" VARCHAR2(20 BYTE), 
          "STATUS" VARCHAR2(20 BYTE)
      );
      
      create or replace type t_testdata as table of t_testdata_rec;
      /
      

      填充包变量的动态 SQL 变得更加复杂:

        execute immediate
        'select cast(collect(t_testdata_rec(id, name, status)) as t_testdata)
        from testdata ' into SESSIONGLOBALS.v_data;
      

      但现在插入将按原样工作。

      【讨论】:

      • +1 cz PLS-00642 提出了正确的问题:如何在 sql 中使用包级集合?生病发布我找到的解决方案。
      猜你喜欢
      • 2017-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多