在 Oracle10gv2 中使用 sys_refcursor 发送嵌套的 table

Send a nested table using sys_refcursor in Oracle10gv2

我需要使用 sys_refcursor 将一些数据发送到 Jasper Reports。此数据是 pl/sql 中查询的结果和对查询结果的评估。这个想法是计算在相同 table 中由多个列过滤的一些值,并且由于过滤限制,这不能在具有 subselect 的查询中完成。抱歉不是很清楚,但我有保密协议。但是,我可以 post 一些代码并解释我必须实现的功能的重要部分。该项目基于Java,使用Oracle 10gv2 和JasperReports 3.6.1,无法更新(因此没有Oracle v12)。

我有一个关联数组的过程,其中填充了键和我必须 return 的值。键表示与目标报表上每个列类型关联的过滤结果,值是必须填充正确列的数字。这是关联数组的过程创建和声明。

create or replace PROCEDURE test_proc02(test_cursor OUT sys_refcursor) IS

    /* associative arrays declaration */

    TYPE transfer_type IS TABLE OF NUMBER
        INDEX BY VARCHAR2(10);
    transfer_table transfer_type; 

但问题之一是我不能像这样使用具有 sys_refcursor 的关联数组:

Select * from table(cast(transfer_table AS transfer_type))

所以我将关联数组值复制到嵌套 Table 中,相信之前的 select 将适用于该结构。这是部分代码

/* nested table declaration */

TYPE transfer_nt_type IS TABLE OF VARCHAR2(20);

/* nested table initilization */
transfer_nt transfer_nt_type := transfer_nt_type();

/* some variables */
transfer_id VARCHAR2(10);
transfer_number NUMBER;
nt_counter INTEGER := 0;
nt_iter VARCHAR2(10);


/* copying AA into NT */
nt_iter := transfer_table.FIRST;
WHILE (nt_iter IS NOT NULL)
LOOP        
    nt_counter := nt_counter+1;
    transfer_nt.EXTEND;
    transfer_nt(nt_counter):=transfer_table(nt_iter);
    dbms_output.put_line('nested table ('||nt_counter||'): '||transfer_nt(nt_counter));
    nt_iter := transfer_table.NEXT(nt_iter);
END LOOP;

/* Trying to send NT to JR */
OPEN travelCursor FOR SELECT * FROM TABLE(cast(transfer_nt AS transfer_nt_type));

/* ERROR */
PLS-00382: expression is of wrong type

我不关心方法,我只是想将数据发送到 JR 以生成报告,所以如果我必须替换完整的 Procedure 结构,我也可以。我在 Whosebug 和其他资源中搜索了几天,但似乎没有任何效果,所以我不确定我的所有概念想法是否有误。

有什么想法吗?谢谢。

编辑:

transfer_nt_type 的类型声明错误,从以前的版本复制而来。现在是正确的。 AA的数据是这样的:

Key       value
--------------
A548521     5
A325411     12
A329471     9

对的总数为 32,键为 varchar2(10),值为数字。最后嵌套的table的内容(VARCHAR(20))为:

A548521#5,A325411#12,A329471#9

类型在架构级别声明。我也试过:

OPEN travelCursor FOR 
        SELECT CAST(MULTISET(
            SELECT * FROM TABLE(transfer_nt)
            ORDER BY 1) AS transfer_nt_type)
        INTO transfer_nt_out FROM DUAL;

结果相同。两种数据结构均已通过 dbms_output 完美测试和打印,结构中的数据是正确的。如果可能的话,我至少需要按照给定的顺序发送这些值。如果我可以在值响应中保持一定的顺序,则键并不重要。

已编辑以反映 Alex Poole 提案。在程序开始之前:

FUNCTION transfer_func (transfer_table transfer_type)RETURN transfer_nt_type PIPELINED IS

      --TYPE transfer_type IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
      --transfer_table transfer_type; 

      nt_iter VARCHAR2(10);

      BEGIN

          nt_iter := transfer_table.FIRST;
          WHILE (nt_iter IS NOT NULL)
          LOOP
            PIPE ROW (nt_iter || '#' || transfer_table(nt_iter));
            nt_iter := transfer_table.NEXT(nt_iter);
          END LOOP;

    END transfer_func;

程序结束前:

OPEN travelCursor for select * from table(transfer_func(transfer_table));

同样的错误:

PLS-00382: expression is of wrong type

最终编辑和解决方案:

最后我用GTT解决了这个问题。我不知道为什么,但我第一次尝试这种方法时,Oracle Developer return 出现了与其他可能的解决方案相同的错误。我尝试了最古老的方法:关闭程序,重置机器并从头开始·而且奏效了!当然,只有 GTT。

nt_iter := transfer_table.FIRST;
WHILE (nt_iter IS NOT NULL)
LOOP        
        nt_counter := nt_counter+1;
        INSERT INTO transfer_temp VALUES(nt_iter,transfer_table(nt_iter),06);

        nt_iter := transfer_table.NEXT(nt_iter);
END LOOP;

    OPEN test_cursor FOR select * from transfer_temp order by transfer_temp.id;

CREATE GLOBAL TEMPORARY TABLE transfer_temp (
        id           VARCHAR(20),
        value         NUMBER,
        month         NUMBER
        )
        ON COMMIT PRESERVE ROWS;

感谢大家的帮助!

一个(也许是低性能)简单的解决方案是将结果写入临时 table,然后

OPEN travelCursor FOR SELECT * FROM That_Temp_Table;

您可以改用流水线函数,使用模式级 table 类型:

create or replace TYPE transfer_nt_type AS TABLE OF VARCHAR2(20)
/

create or replace FUNCTION test_func02
RETURN transfer_nt_type PIPELINED IS

  TYPE transfer_type IS TABLE OF NUMBER INDEX BY VARCHAR2(10);
  transfer_table transfer_type; 

  nt_iter VARCHAR2(10);

BEGIN

  -- sample data
  transfer_table('A548521') := 5;
  transfer_table('A325411') := 12;
  transfer_table('A329471') := 9;

  nt_iter := transfer_table.FIRST;
  WHILE (nt_iter IS NOT NULL)
  LOOP
    PIPE ROW (nt_iter || '#' || transfer_table(nt_iter));
    nt_iter := transfer_table.NEXT(nt_iter);
  END LOOP;

  RETURN;

END test_func02;
/

那么你可以这样做:

select * from table(test_func02);

得到:

Result Sequence     
--------------------
A325411#12
A329471#9
A548521#5

如果您仅限于引用游标,那么您可以添加一个包装程序:

create or replace PROCEDURE test_proc02(test_cursor OUT sys_refcursor) IS
BEGIN

  OPEN test_cursor FOR SELECT * FROM TABLE(test_func02);

END test_proc02;
/

var rc refcursor;

exec test_proc02(:rc);

print rc;

Result Sequence     
--------------------
A325411#12
A329471#9
A548521#5

我相信你有问题,因为:

1) You defined your type inside procedure

所以尝试像上面的例子一样创建类型

CREATE OR REPLACE TYPE "TSTRINGTABLE" AS TABLE OF VARCHAR2(20)
/

2) You use select * from on indexed table...

如果您需要在嵌套 table 中存储 2 个值,则创建 table 类型的记录,其中的记录类似于

CREATE OR REPLACE TYPE "TSTRING2LIST_RECORD" AS OBJECT
(
  column1_value VARCHAR2(4000),
  column2_value VARCHAR2(4000)
)
/

CREATE OR REPLACE TYPE "TSTRING2LIST_TABLE" IS TABLE OF TSTRING2LIST_RECORD
/

在那之后你可以在你的程序中声明它并随心所欲地使用它,我下面的代码可能会被破坏,因为我写的时候没有 IDE,但是你需要理解我的想法:

PROCEDURE idk(cur out sys_refcursor) IS
  l_table tstring2list_table := tstring2list_table();

BEGIN
  l_table.extend;
  l_table(l_table.count) := tstring2list_record('idx1', 'value1');
  l_table.extend;
  l_table(l_table.count) := tstring2list_record('idx2', 'value2');
  l_table.extend;
  l_table(l_table.count) := tstring2list_record('idx3', 'value3');

  OPEN cur for select column2_value from table(l_table);
END;

您还可以使用批量收集或动态 SQL 或其他任何方式:

SELECT tstring2list_record(t.col1, t.col2) BULK COLLECT
  INTO l_table
  FROM some_table t