Return deleted rows in a cursor in PL/SQL

682 views Asked by At

I have a requirement where I want to return the deleted records in a sys_refcursor. I can retrieve the data in cursor before the delete statement but is there any way to retrieve them after a delete statement? I mean process will be like at first delete and then open sys_refcursor for fetching deleted records.

1

There are 1 answers

0
Kevin Seymour On

There are probably a few options, but one option would be to use RETURNING and BULK COLLECT. Here is a simple example.

CREATE TABLE t (
       a NUMBER,
       b VARCHAR2(10),
       c DATE
);

INSERT INTO t VALUES (1, 'a', SYSDATE);
INSERT INTO t VALUES (2, 'b', SYSDATE);
INSERT INTO t VALUES (3, 'c', SYSDATE);
INSERT INTO t VALUES (4, 'd', SYSDATE);
INSERT INTO t VALUES (5, 'e', SYSDATE);

CREATE OR REPLACE TYPE tt AS OBJECT (
       a NUMBER,
       b VARCHAR2(10),
       c DATE
);

CREATE OR REPLACE TYPE tt_tab AS TABLE OF tt;

DECLARE
  v_tt_tab tt_tab;
  v_tt     tt;
  v_cur    SYS_REFCURSOR;
BEGIN
  DELETE FROM t
   WHERE a < 4
  RETURNING tt(a, b, c) BULK COLLECT INTO v_tt_tab;

  OPEN v_cur FOR
    SELECT tt(a,
              b,
              c)
      FROM TABLE(v_tt_tab);

  LOOP
    FETCH v_cur
      INTO v_tt;
    EXIT WHEN v_cur%NOTFOUND;
  
    dbms_output.put_line(v_tt.a || ' ' || v_tt.b || ' ' || v_tt.c);
  END LOOP;

  CLOSE v_cur;
END;
/

/*
1 a 07-OCT-20
2 b 07-OCT-20
3 c 07-OCT-20
*/

By creating an object that matches the data we want to keep and a table of that object we can return that into a collection and then easily turn it back into a cursor using the TABLE function.

You need to be careful with how many rows you delete as you don't want to run out of memory. I would be cautious with this approach if you delete more than a few hundred rows.

Another suggestion might be to use a GTT, INSERT the rows you plan to delete and then delete from the first table using the GTT as your "key".

CREATE GLOBAL TEMPORARY TABLE t_gtt (
       a NUMBER,
       b VARCHAR2(10),
       c DATE
);

DECLARE
  v_tt_tab tt_tab;
  v_tt     tt;
  v_cur    SYS_REFCURSOR;
BEGIN
  INSERT INTO t_gtt
    SELECT *
      FROM t
     WHERE a < 4;

  DELETE FROM t
   WHERE EXISTS (SELECT NULL
            FROM t_gtt
           WHERE t_gtt.a = t.a);

  OPEN v_cur FOR
    SELECT tt(a,
              b,
              c)
      FROM t_gtt;

  LOOP
    FETCH v_cur
      INTO v_tt;
    EXIT WHEN v_cur%NOTFOUND;
  
    dbms_output.put_line(v_tt.a || ' ' || v_tt.b || ' ' || v_tt.c);
  END LOOP;

  CLOSE v_cur;
END;
/

This option is maybe better if you are planning to delete a large amount of rows. I used my tt object again, but you really don't need it. It just made looping to dump the contents of the SYS_REFCURSOR easier.