Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting Values from Oracle Table Variable / Array?

Tags:

oracle

plsql

Following on from my last question (Table Variables in Oracle PL/SQL?)...

Once you have values in an array/table, how do you get them back out again? Preferably using a select statement or something of the like?

Here's what I've got so far:

declare
    type array is table of number index by binary_integer;
    pidms array;
begin
    for i in    (
                select distinct sgbstdn_pidm
                from sgbstdn
                where sgbstdn_majr_code_1 = 'HS04'
                and sgbstdn_program_1 = 'HSCOMPH'
                )
    loop
        pidms(pidms.count+1) := i.sgbstdn_pidm;
    end loop;

    select *
    from pidms; --ORACLE DOESN'T LIKE THIS BIT!!!
end;

I know I can output them using dbms_output.putline(), but I'm hoping to get a result set like I would from selecting from any other table.

Thanks in advance, Matt

like image 532
Sonny Boy Avatar asked Oct 15 '09 17:10

Sonny Boy


People also ask

What is SELECT * from table in Oracle?

In short, it is used to convert a collection or pipelined function into a table that can be queried by a SELECT statement. Typically, the collection must be of a datatype that is defined at the database level (i.e. a datatype that was created by a create or replace type ... statement). e.g.

How do you check if a column contains a particular value in Oracle?

The syntax for creating a check constraint using a CREATE TABLE statement in Oracle is: CREATE TABLE table_name ( column1 datatype null/not null, column2 datatype null/not null, ... CONSTRAINT constraint_name CHECK (column_name condition) [DISABLE] ); The DISABLE keyword is optional.

What is the difference between Varray and nested table in Oracle?

Differences Between Varrays And Nested Tables A Varray which is stored in a database maintains its subscripts and sequence. It is always maintained as a single object. Whereas, the nested tables are used when the count of the number of elements is not restricted.

What does %type do in Oracle?

The %TYPE attribute lets you declare a constant, variable, collection element, record field, or subprogram parameter to be of the same data type as a previously declared variable or column (without knowing what that type is).


Video Answer


3 Answers

The sql array type is not neccessary. Not if the element type is a primitive one. (Varchar, number, date,...)

Very basic sample:

declare
  type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
  pidms TPidmList;
begin
  select distinct sgbstdn_pidm
  bulk collect into pidms
  from sgbstdn
  where sgbstdn_majr_code_1 = 'HS04'
  and sgbstdn_program_1 = 'HSCOMPH';

  -- do something with pidms

  open :someCursor for
    select value(t) pidm
    from table(pidms) t;
end;

When you want to reuse it, then it might be interesting to know how that would look like. If you issue several commands than those could be grouped in a package. The private package variable trick from above has its downsides. When you add variables to a package, you give it state and now it doesn't act as a stateless bunch of functions but as some weird sort of singleton object instance instead.

e.g. When you recompile the body, it will raise exceptions in sessions that already used it before. (because the variable values got invalided)

However, you could declare the type in a package (or globally in sql), and use it as a paramter in methods that should use it.

create package Abc as
  type TPidmList is table of sgbstdn.sgbstdn_pidm%type;

  function CreateList(majorCode in Varchar, 
                      program in Varchar) return TPidmList;

  function Test1(list in TPidmList) return PLS_Integer;
  -- "in" to make it immutable so that PL/SQL can pass a pointer instead of a copy
  procedure Test2(list in TPidmList);
end;

create package body Abc as

  function CreateList(majorCode in Varchar, 
                      program in Varchar) return TPidmList is
    result TPidmList;
  begin
    select distinct sgbstdn_pidm
    bulk collect into result
    from sgbstdn
    where sgbstdn_majr_code_1 = majorCode
    and sgbstdn_program_1 = program;

    return result;
  end;

  function Test1(list in TPidmList) return PLS_Integer is
    result PLS_Integer := 0;
  begin
    if list is null or list.Count = 0 then
      return result;
    end if;

    for i in list.First .. list.Last loop
      if ... then
        result := result + list(i);
      end if;
    end loop;
  end;

  procedure Test2(list in TPidmList) as
  begin
    ...
  end;

  return result;
end;

How to call it:

declare
  pidms constant Abc.TPidmList := Abc.CreateList('HS04', 'HSCOMPH');
  xyz PLS_Integer;
begin
  Abc.Test2(pidms);
  xyz := Abc.Test1(pidms);
  ...

  open :someCursor for
    select value(t) as Pidm,
           xyz as SomeValue
    from   table(pidms) t;
end;
like image 97
Robert Giesecke Avatar answered Sep 28 '22 22:09

Robert Giesecke


In Oracle, the PL/SQL and SQL engines maintain some separation. When you execute a SQL statement within PL/SQL, it is handed off to the SQL engine, which has no knowledge of PL/SQL-specific structures like INDEX BY tables.

So, instead of declaring the type in the PL/SQL block, you need to create an equivalent collection type within the database schema:

CREATE OR REPLACE TYPE array is table of number;
/

Then you can use it as in these two examples within PL/SQL:

SQL> l
  1  declare
  2    p  array := array();
  3  begin
  4    for i in (select level from dual connect by level < 10) loop
  5      p.extend;
  6      p(p.count) := i.level;
  7    end loop;
  8    for x in (select column_value from table(cast(p as array))) loop
  9       dbms_output.put_line(x.column_value);
 10    end loop;
 11* end;
SQL> /
1
2
3
4
5
6
7
8
9

PL/SQL procedure successfully completed.

SQL> l
  1  declare
  2    p  array := array();
  3  begin
  4    select level bulk collect into p from dual connect by level < 10;
  5    for x in (select column_value from table(cast(p as array))) loop
  6       dbms_output.put_line(x.column_value);
  7    end loop;
  8* end;
SQL> /
1
2
3
4
5
6
7
8
9

PL/SQL procedure successfully completed.

Additional example based on comments

Based on your comment on my answer and on the question itself, I think this is how I would implement it. Use a package so the records can be fetched from the actual table once and stored in a private package global; and have a function that returns an open ref cursor.

CREATE OR REPLACE PACKAGE p_cache AS
  FUNCTION get_p_cursor RETURN sys_refcursor;
END p_cache;
/

CREATE OR REPLACE PACKAGE BODY p_cache AS

  cache_array  array;

  FUNCTION get_p_cursor RETURN sys_refcursor IS
    pCursor  sys_refcursor;
  BEGIN
    OPEN pCursor FOR SELECT * from TABLE(CAST(cache_array AS array));
    RETURN pCursor;
  END get_p_cursor;

  -- Package initialization runs once in each session that references the package
  BEGIN
    SELECT level BULK COLLECT INTO cache_array FROM dual CONNECT BY LEVEL < 10;
  END p_cache;
/
like image 28
Dave Costa Avatar answered Sep 28 '22 22:09

Dave Costa


You might need a GLOBAL TEMPORARY TABLE.

In Oracle these are created once and then when invoked the data is private to your session.

Oracle Documentation Link

Try something like this...

CREATE GLOBAL TEMPORARY TABLE temp_number
   ( number_column   NUMBER( 10, 0 )
   )
   ON COMMIT DELETE ROWS;

BEGIN 
   INSERT INTO temp_number
      ( number_column )
      ( select distinct sgbstdn_pidm 
          from sgbstdn 
         where sgbstdn_majr_code_1 = 'HS04' 
           and sgbstdn_program_1 = 'HSCOMPH' 
      ); 

    FOR pidms_rec IN ( SELECT number_column FROM temp_number )
    LOOP 
        -- Do something here
        NULL; 
    END LOOP; 
END; 
/
like image 24
PaulJ Avatar answered Sep 28 '22 21:09

PaulJ