tags:

views:

1547

answers:

6

I need to return a rowset from an Oracle procedure, and then delete them in that same procedure. Is there a neat way of doing this without temp tables? Something like an in-memory cursor maybe?

Basically I'm popping the records off a queue, and I want to avoid two round trips because it's a very frequent process.

+3  A: 

You can use a cursor for update, e.g.

DECLARE
  CURSOR c_updates
  IS
    SELECT *
    FROM table1 t1
    LEFT JOIN table2 t2 ON t1.field = t2.field
    WHERE t2.field IS NULL
  FOR UPDATE OF t1.field;

  l_record c_updates%ROWTYPE;
BEGIN
  OPEN c_updates;

  LOOP
    FETCH c_updates INTO l_record;
    EXIT WHEN c_updates%NOTFOUND;

    --Do what you want with l_record

    DELETE FROM table1
    WHERE CURRENT OF c_updates;
  END LOOP;

  CLOSE c_updates;
END;
Reuben Peeris
+2  A: 

Populate the data into an TYPE and return that?

e.g.

CREATE TYPE blah as (data-columns-go-here)
/

CREATE TYPE blah_table AS TABLE OF blah;
/
cagcowboy
+2  A: 

Building on reubenpeeris' answer and cagcowboy's answer:

WARNING: I don't have access to a PL/SQL compiler at the moment, so there is a chance that something is wrong.

TYPE popped_records_table_type IS TABLE OF my_table%ROWTYPE INDEX BY BINARY_INTEGER;

FUNCTION pop_records(...) RETURN popped_records_table_type IS
    popped_records popped_records_table_type;
    popped_record my_table%ROWTYPE;
    next_popped_record_index BINARY_INTEGER;

    CURSOR popped_records_cursor IS
        SELECT * FROM my_table WHERE ... FOR UPDATE;
BEGIN
    next_popped_record_index := 1;

    OPEN popped_records_cursor;

    LOOP
        FETCH popped_records_cursor INTO popped_record;
        EXIT WHEN popped_records_cursor%NOTFOUND;

        DELETE FROM my_table WHERE CURRENT OF popped_records_cursor;

        popped_records(next_popped_record_index) := popped_record;
        next_popped_record_index := next_popped_record_index + 1;
    END LOOP;

    CLOSE popped_records_cursor;

    RETURN popped_records;
END;

Edit: I believe this will also work with a stored procedure, so long as you provide an instance of the popped_records_table_type as an IN/OUT parameter:

PROCEDURE pop_records(popped_records IN OUT popped_records_table_type, ...) IS
    -- Pretty much the same as above
Adam Paynter
+2  A: 

You can return a cursor from a procedure or an anonymous block:

BEGIN
        OPEN :cur FOR
        SELECT  *
        FROM    table
        WHERE   condition;

        DELETE
        FROM    table
        WHERE   condition;

END;

The cursor will persist after delete.

See the entry in my blog for detailed explanations:

, and here is this entry in a nutshell:

CREATE TABLE t_deleter (id INT NOT NULL PRIMARY KEY, value VARCHAR2(50))
/
INSERT
INTO    t_deleter (id, value)
VALUES (1, 'Value 1')
/
INSERT
INTO    t_deleter (id, value)
VALUES (2, 'Value 2')
/
COMMIT
/
SELECT  *
FROM    t_deleter
/
VAR cur REFCURSOR
BEGIN
        OPEN    :cur FOR
        SELECT  *
        FROM    t_deleter
        WHERE   id = 1;
        DELETE
        FROM    t_deleter
        WHERE   id = 1;
END;
/
PRINT cur
SELECT  *
FROM    t_deleter
/

Table created.


1 row created.


1 row created.


Commit complete.


        ID VALUE
---------- --------------------------------------------------
         1 Value 1
         2 Value 2


PL/SQL procedure successfully completed.


/*
   PRINT CUR
   This is what returned to the client
*/

        ID VALUE
---------- --------------------------------------------------
         1 Value 1

/*
   SELECT  *
   FROM    t_deleter

   This is what's left after the procedure completed
*/


        ID VALUE
---------- --------------------------------------------------
         2 Value 2
Quassnoi
+7  A: 

Actually, you can do it without a SELECT these days. You can simply DELETE the records you are interested in and use the RETURNING clause to fetch those records into a local variable as they are deleted.

DELETE FROM my_table
  WHERE <whatever conditions>
  RETURNING column1, column2, ...
  INTO array1, array2, ...

The slightly annoying part of this method is that you need to fetch each column into a separate variable. You can't use a record type in this context. So if you have a lot of columns it can get cumbersome.

Dave Costa
A: 

Oracle has something call advanced queuing, maybe it is better to use that feature than building your own queuing system.

tuinstoel
i would look into that, but we're trying not to bind to oracle too closely
James L