简体   繁体   中英

Pipelined function doesn't return a table with errors

I need to insert some values into a table from a collection in a procedure, but I'm getting an ORA-00902: invalid datatype error.

This is a target table:

create table t_test (col01 number, col02 number);

I'm defining a type of collection and a pipelined function in a package:

create or replace package p_test is 
  type t_num is table of number;
  function rtn(arg_tn t_num) return t_num PIPELINED;
end p_test;
/
create or replace package body p_test is 
  function rtn(arg_tn t_num) return t_num PIPELINED is 
      tn_row number;
    begin 
      for i in arg_tn.first .. arg_tn.last loop
        tn_row := arg_tn(i);
        pipe row(tn_row);
      end loop;
      return;
  end;
end p_test;

And this is my PL/SQL procedure:

declare
  tn_test p_test.t_num := p_test.t_num(10,20,30);
  n_num number := 69;
begin
  insert into T_TEST(col01, col02) select n_num, column_value from table(tn_test);
end;

Resulting table would look something like this:

 col01 | col02
-------|-------
 69    | 10
 69    | 20
 69    | 30

And this is the error I'm getting:

错误6550、642、306

What am I doing wrong? How to fix it? I could've done this in a for cycle, but isn't it too inefficient for required purpose?

The locally defined PL/SQL collection types cannot be used within a non-query DML statement (eg as an argument to a table function). Just move the initialization of the test data to the table function. Consider the following example:

create or replace package p_test is 
    type t_num is table of number;
    function rtn return t_num pipelined;
end p_test;
/
create or replace package body p_test is 
    function rtn return t_num pipelined is 
        tn_test t_num := t_num (10, 20, 30);
    begin 
        for i in 1..tn_test.count loop
            pipe row (tn_test (i));
        end loop;
        return;
    end;
end p_test;
/

begin
    insert into t_test (col01, col02) 
    select rownum, column_value from table (p_test.rtn ())
    ;
    dbms_output.put_line (sql%rowcount||' rows inserted.');  
end;
/
select * from t_test;     

3 rows inserted.

     COL01      COL02
---------- ----------
         1         10
         2         20
         3         30

If the arguments of the collection type are essential, use either the FORALL statement, or the SQL data types as suggested in the @XING answer .

Demo with the package as in the question without any change:

declare 
    sources p_test.t_num := p_test.t_num (10,20,30);
    targets p_test.t_num; 
    retrows p_test.t_num; 
    n_num number := 69;
begin
    select * bulk collect into targets
    from table (p_test.rtn (sources))
    ;
    forall i in indices of targets
    insert into t_test (col01, col02) values (n_num, targets (i)) 
    returning col01 bulk collect into retrows
    ;
    dbms_output.put_line (retrows.count||' rows inserted.'); 
end;
/

3 rows inserted.

select * from t_test;

     COL01      COL02
---------- ----------
        69         10
        69         20
        69         30

What am I doing wrong? How to fix it? I could've done this in a for cycle, but isn't it too inefficient for required purpose?

If you inspect the error properly you could see the error. The error says:

Local Collection Types are not allowed in SQL statement

Which means in your execution block:

insert into T_TEST(col01, col02) select n_num, column_value from table(tn_test);

Above statement is NOT ALLOWED .

Until Oracle 11g, you cannot use a Type declared under the scope if PLSQL block directly under SQL statement used inside the block. You need to change the scope of declaration of Type outside the PLSQL scope. Which means, you need to REMOVE

type t_num is table of number; from the package specification and create a TYPE outside the in SQL scope.

So you can do this:

Create or replace type t_num is table of number; 

See below demo:

create table t_test (col01 number, col02 number);

-- Moving the type decalration under the scope of SQL.
Create or replace type t_num is table of number;

create or replace package p_test is
--  type t_num is table of number; --<-- Commenting the declaration since this is not allowed until 11g.
  function rtn(arg_tn t_num) 
    return t_num  PIPELINED;
end p_test;
/

create or replace package body p_test is
  function rtn(arg_tn t_num) 
    return t_num PIPELINED 
   is
    tn_row number;
  begin
    for i in arg_tn.first .. arg_tn.last
    loop
      tn_row := arg_tn(i);
      pipe row(tn_row);
    end loop;
    return;
  end;
end p_test;

Execution:

declare
  tn_test t_num := t_num(10, 20, 30);
  n_num   number := 69;
begin
  insert into T_TEST
    (col01,
     col02)
    select n_num,
           column_value
    from   table(tn_test);
    commit;
end;

Test:

 SQL> Select * from T_TEST;

     COL01      COL02
---------- ----------
        69         10
        69         20
        69         30

Because there's a type mismatch for that INSERT statement among col02 and column_value , which are of type number and one-dimensional array , respectively. If an iteration is applied to that array, the individual numbers are derived for each iteration step. It can be managed through use of a cursor :

declare
  tn_test p_test.t_num := p_test.t_num(10,20,30);
  n_num number := 69;
begin
  for c in ( select row_number() over (order by 0) rn, column_value from table(tn_test) t)
  loop
    insert into t_test(col01,col02) values(n_num, tn_test(c.rn));
  end loop;
end;

That way, you'll get

select * from t_test;

+------+------+
|col01 |col02 |
+------+------+
|  69  | 10   |
|  69  | 20   |
|  69  | 30   |
+------+------+

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM