Search code examples
sqloraclestored-proceduresplsqlcursor

Using function inside a cursor (by using a variable)


I would like to confirm the correct use of the following:

1) Use a global variable to get return values from a function only once (since my function will be returning some Sequence values)

2) Use that variable inside a cursor multiple times

3) All of these will be inside a procedure

Below shows a sample.

CREATE OR REPLACE Procedure insert_myTable is
  --declare variables for insert
  v_firstNO VARCHAR2(10);
  v_secondNO VARCHAR2(6);

  --declare variable to store the sequence number
  var_ASeqno varchar2(6);

  -- Validation
  v_check VARCHAR2 (10 Byte);
  v_table_name varchar2(50):='myTable';

  cursor c1 is
    select distinct firstNO,
                    secondNO
      from (SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno secondNO
              FROM someOtherTable hdr
              WHERE -- some condition
            union
            SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno  secondNO
              FROM someOtherTable hdr
              WHERE -- some other conditions
            union
            SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno secondNO
              FROM someOtherTable hdr
              WHERE -- some other other conditions


begin
  if c1%isopen then
    close c1;
  end if;

  v_check:=null;

  FOR i IN c1 LOOP
    --assign variables for insert
    v_firstNO := i.firstNO ;
    v_secondNO  := i.secondNO ;

    begin
      -- calling the Function aSeqNoFunc and assign the
      --Sequence Number into the variable var_ASeqno
      var_ASeqno := aSeqNoFunc();

      select firstNO
        into v_check
        from myTable a
        where firstNO = i.firstNO
              and secondNO =i.secondNO;
    exception
      when no_data_found then
        --insert into target table
        INSERT INTO myTable (firstNO, secondNO)  
          values (v_firstNO, v_secondNO);
    end ;
  end loop;
end;

As can be seen, the function 'aSeqNoFunc' is called before the Insert near the end. The values are assigned to the variable 'var_ApmSeqno' which in turn is used three times inside the cursor.

Thank you.


Solution

  • Taking into account the fact that you want all the rows being inserted to have the same sequence number assigned, I think you could probably rewrite your procedure to something like:

    create or replace procedure insert_mytable
    is
      v_seq_no number;
    begin
      v_seq_no := somesequence.nextval;
    
      merge into mytable tgt
      using (select firstno,
                    v_seq_no secondno
             from   (select hdr.somenum firstno
                     from   someothertable1 hdr
                     where  -- some condition
                     union
                     select hdr.somenum firstno
                     from   someothertable2 hdr
                     where  -- some other conditions
                     union
                     select hdr.somenum firstno
                     from   someothertable3 hdr
                     where  -- some other other conditions
                     )
             ) src
        on (tgt.firstno = src.firstno and tgt.secondno = src.secondno)
      when not matched then
        insert (tgt.firstno, tgt.secondno)
        values (src.firstno, src.secondno);
    end insert_mytable;
    /
    

    If this doesn't match with what you're trying to do, please edit your question to provide more information on what the aim of the procedure is. Example input and output data would be appreciated, so that we have a better idea of what you're wanting (since we can't see your table structures, data, etc).


    ETA: Info on performance considerations between set-based and row-by-row approaches.

    Here's a simple script to insert of a million rows, both row-by-row and as a single insert statement:

    create table test (col1 number,
                       col2 number);
    
    set timing on;
    
    -- row-by-row (aka slow-by-slow) approach
    begin
      for rec in (select level col1, level * 10 col2
                  from   dual
                  connect by level <= 1000000)
      loop
        insert into test (col1, col2)
        values (rec.col1, rec.col2);
      end loop;
    end;
    /
    
    commit;
    truncate table test;
    
    -- set based approach (keeping in an anonymous block for comparison purposes)
    begin  
      insert into test (col1, col2)
      select level, level*10
      from   dual
      connect by level <= 1000000;
    end;
    /
    
    commit;
    
    drop table test;
    

    Here's the output I get, when I run the above in Toad:

    Table created.
     PL/SQL procedure successfully completed.
    Elapsed: 00:00:21.87
    Commit complete.
    Elapsed: 00:00:01.03
    Table truncated.
    Elapsed: 00:00:00.22
     PL/SQL procedure successfully completed.
    Elapsed: 00:00:01.96
    Commit complete.
    Elapsed: 00:00:00.03
    Table dropped.
    Elapsed: 00:00:00.18
    

    Do you see the elapsed time of 21 seconds for the row-by-row approach, and 2 seconds for the set-based approach? Massive difference in performance, don't you agree? If that's not reason to consider writing your code as set based in the first instance, then I don't know what else will convince you/your boss!