5

I'm trying to create a stored function in oracle that returns multiple rows.

My question is very similar to this one except that I want to fetch a select * query

In a nutshell, I want to create a function which returns the result of this query

select * from t_email_queue

What I've tried is this :

create or replace
PACKAGE email_queue AS 

  type t_email_queue_type is table of T_EMAIL_QUEUE%ROWTYPE;  

  FUNCTION lock_and_get return t_email_queue_type;

END email_queue;

create or replace
PACKAGE BODY email_queue AS 

    FUNCTION lock_and_get RETURN t_email_queue_type AS 
      queue_obj t_email_queue_type;

      cursor c (lockid in varchar2) is select * from t_email_queue where lockedby = lockid;
      lockid varchar2(100) := 'alf';
    BEGIN

      OPEN c(lockid);
      FETCH c bulk collect INTO queue_obj;

      return queue_obj;

    END lock_and_get;

END email_queue;

The package compiles just fine but when I try to call it with this query

select * from table(email_queue.lock_and_get);

Oracle throws the following error

ORA-00902: invalid datatype
00902. 00000 -  "invalid datatype"
*Cause:    
*Action:
Error at Line: 1 Column: 20

I think Oracle want me to create my return type at the schema level but when I try to do

create type t_email_queue_type is table of T_EMAIL_QUEUE%ROWTYPE;  

Oracle complains

Type IMAIL.T_EMAIL_QUEUE_TYPE@imail dev
Error(1): PL/SQL: Compilation unit analysis terminated
Error(2,37): PLS-00329: schema-level type has illegal reference to IMAIL.T_EMAIL_QUEUE

Can someone point me to the right direction? What am I missing here?

Thanks for reading!

Community
  • 1
  • 1
Alfwed
  • 3,307
  • 2
  • 18
  • 20

3 Answers3

5

with SQL types you cant do %ROWTYPE, you'd have to type each column to match the table*.

*sys.anydataset aside. but going down that route is a lot more complex coding.

e.g. if your table was

create table foo (id number, cola varchar2(1));

then

create type email_queue_type is object (id number, cola varchar2(1));
/
create type t_email_queue_type as table of email_queue_type;
/

and use that table email_queue_type_tab as the output from your function.

but i'd recommend a pipelined function, as your current code isn't scalable.

eg:

SQL> create table foo (id number, cola varchar2(1));

Table created.

SQL>
SQL> create type email_queue_type is object (id number, cola varchar2(1));
  2  /

Type created.

SQL> create type t_email_queue_type as table of email_queue_type;
  2  /

Type created.

SQL> insert into foo select rownum, 'a' from dual connect by level <= 10;

10 rows created.

SQL>
SQL> create or replace PACKAGE email_queue AS
  2
  3
  4    FUNCTION lock_and_get return t_email_queue_type pipelined;
  5
  6  END email_queue;
  7  /

Package created.

SQL> create or replace PACKAGE BODY email_queue AS
  2
  3      FUNCTION lock_and_get RETURN t_email_queue_type pipelined AS
  4        queue_obj t_email_queue_type;
  5
  6      BEGIN
  7
  8       for r_row in (select * from foo)
  9              loop
 10                pipe row(email_queue_type(r_row.id, r_row.cola));
 11              end loop;
 12
 13      END lock_and_get;
 14
 15  END email_queue;
 16  /

Package body created.

SQL> select * from table(email_queue.lock_and_get());

        ID C
---------- -
         1 a
         2 a
         3 a
         4 a
         5 a
         6 a
         7 a
         8 a
         9 a
        10 a

10 rows selected.

SQL>
DazzaL
  • 21,638
  • 3
  • 49
  • 57
  • Problem with this solution is that when I'll have to add a column to the table `t_email_queue`, I'll have to modify my type `email_queue_type` as well and it's a pain; I'll have to stop my PHP scripts calling the package while I'm modifying the type and the package and this isn't an option. – Alfwed Dec 12 '12 at 17:50
  • @Alfwed are you on 11g. if so "editions" can mean you can update the type + package/type without stopping the PHP app for this. – DazzaL Dec 12 '12 at 18:02
  • Yes I'm running 11g. This function will we called roughly every second. You're saying i'll be able to edit the table, type and package between 2 calls? – Alfwed Dec 12 '12 at 18:13
  • 2
    @Alfwed well you had the table problem before, but like i said with editions you edit the type + package in a new edition (so running code cant see it). then when finished you set that as active, so any new calls do see it. – DazzaL Dec 12 '12 at 18:48
  • eg call A comes in, then you edit the type (in a new edition) then call B comes in. call B will still see the old type. i recommend you read about it. eg see here http://www.youtube.com/watch?v=cZSIaZCWaOs&t=7m6s – DazzaL Dec 12 '12 at 18:53
  • Well I guess this is the right answer. Too bad that oracle can't achieve what I wanted. – Alfwed Dec 21 '12 at 09:30
5

If you aren't particularly keen on having the SQL type, you could do this with a sys_refcursor instead:

create or replace package email_queue as 
    function lock_and_get return sys_refcursor;
end email_queue;
/

create or replace package body email_queue as 
    function lock_and_get return sys_refcursor AS 
        c sys_refcursor;
        lockid varchar2(100) := 'alf';
    begin
        open c for
            select * from t_email_queue
            where lockedby = lockid;

         return c;
    end lock_and_get;
end email_queue;
/

From SQL*Plus you could call it something like:

var cur refcursor;
exec :cur := email_queue.lock_and_get;
print cur

and as exec is shorthand for a simple anonymous block you can call if from other PL/SQL objects too. What you can't do, though, is:

select * from table(email_queue.lock_and_get);

I'm not familiar with calling functions from PHP, but from Java you can use this directly as the return from a callable statement, so you don't need the select * from table() construct at all. I have no idea if you can execute an anonymous block in a PHP call, something like begin $cur = email_queue.lock_and_get; end;, and have $cur as your result set that you can then iterate through?

I realise this isn't a complete answer as the PHP side is way too vague, but might give you some ideas.

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Good answer. I'm currently using this technic but I don't like it much because the call to this kind of function from PHP is kinda weird. Hence my question about the "table return types". – Alfwed Dec 13 '12 at 09:16
  • @Alfwed - maybe a meaningless distinction, but returning the ref cursor as an `out` parameter in a procedure is also an option. Probably no less weird in PHP, but I have no idea *8-) – Alex Poole Dec 13 '12 at 10:33
1

If you are using PHP and you want to access a oracle stored function. You can make use of something like this

//Your connection details
$conn = oci_connect($username, $password, '(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = XE)))' );

/* Your query string; you can use oci_bind_by_name to bind parameters or just pass the variable in it*/

$query = "begin :cur := functionName('".$param1."','".$param2."','".$param3."'); end;";
$stid = oci_parse($conn, $query); 
$OUTPUT_CUR = oci_new_cursor($conn);
oci_bind_by_name($stid, ':cur', $OUTPUT_CUR, -1, OCI_B_CURSOR);
oci_execute($stid); 
oci_execute($OUTPUT_CUR);
oci_fetch_all($OUTPUT_CUR, $res);

// To get your result  
var_dump($res);

I hope this helps.

Alfwed
  • 3,307
  • 2
  • 18
  • 20
Deolu Philip
  • 197
  • 2
  • 5