1

我有一个包,它声明了一些数据库表的 %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 := '从测试数据中选择 *';)

4

3 回答 3

2

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

请参阅: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 * from testdata 完全没有可比性;从性能的角度来看,但我会在一些更复杂的用例中尝试这个概念。目标是避免执行一些非常庞大的选择语句,这些语句涉及分布在多个模式中的大量表(模式的复数形式......?)。

于 2013-06-12T09:41:25.463 回答
1

您使用的语法不起作用:

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);

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

于 2013-06-07T12:47:34.613 回答
1

包级类型不能在 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;

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

于 2013-06-09T04:50:47.857 回答