Search code examples
oracle-databaseplsqltypesoracle10gplsqldeveloper

HOW TO FILL A TYPE OR COLLECTION WITH NUMBERS FROM 10M TO 30M IN PLSQL ORACLE


Hello stackoverflow friends!

Please help, i have to assign a value since 100 to 300 in an cursor or a type or a table (i don't know which of them are better for this project). First i want to fill the type or cursor with those values (10M to 30M) and then choose randomly one of them but just once, i mean, it can't pick 102 twice, for example. There are millions of entries and i wouldn't want to affect the performance of the database. I tried do it with a cursor and n+1 but its so slow...

Thanks my Oracle's friends for your help and suggestions.


Solution

  • I believe n + 1 is slow; if you do it that way, it certainly takes time to insert 20 million rows one by one. Another option is to use row generator.

    I have 21XE on laptop (nothing special; Intel i5, 8GB RAM) which isn't enough memory to do it in one go:

    SQL> create table test (id number);
    
    Table created.
    
    SQL> insert into test
      2  select 10e6 + level - 1
      3  from dual
      4  connect by level <= 20e6;
    insert into test_20mil
                *
    ERROR at line 1:
    ORA-30009: Not enough memory for CONNECT BY operation
    
    
    SQL>
    

    If you ask whether it really works?, the answer is yes - see for a small sample:

    SQL> select level
      2  from dual
      3  connect by level <= 5;
    
         LEVEL
    ----------
             1
             2
             3
             4
             5
    
    SQL>
    

    Therefore, I used a loop to do it 20 times, each of them inserting 1 million rows at a time into a table (not row-by-row). Why not collection? Memory issues!

    SQL> create table test (id number primary key, cb_picked number(1));
    
    Table created.
    
    SQL> set serveroutput on
    SQL> set timing on
    SQL> declare
      2    l_mil number := 10e6;
      3  begin
      4    for i in 1 .. 20 loop
      5      insert into test (id)
      6        select l_mil + level - 1
      7        from dual
      8        connect by level <= 1e6;
      9      dbms_output.put_Line('inserted ' || sql%rowcount);
     10      l_mil := l_mil + 1e6;
     11    end loop;
     12  end;
     13  /
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    inserted 1000000
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:01:25.77
    

    As you can see, it took slightly less than minute and a half.

    SQL> set timing off
    SQL> select count(*) from test;
    
            COUNT(*)
    ----------------
          20,000,000
    
    SQL> select min(id), max(id) from test;
    
             MIN(ID)          MAX(ID)
    ---------------- ----------------
          10,000,000       29,999,999
    
    SQL>
    

    That was inserting; what about fetching randomly picked rows? Use dbms_random.value function. To avoid selecting already picked values twice, update table's cb_picked column. To do that, create an autonomous transaction function (why? So that you could perform DML - update - and return value).

    SQL> create or replace function f_rnd
      2    return number
      3  is
      4    pragma autonomous_transaction;
      5    retval number;
      6  begin
      7    select id
      8      into retval
      9      from test
     10      where cb_picked is null
     11        and id = round(dbms_random.value(10e6, 20e6));
     12    update test set cb_picked = 1
     13      where id = retval;
     14    commit;
     15    return retval;
     16  end;
     17  /
    
    Function created.
    

    Let's try it:

    SQL> select f_rnd from dual;
    
         F_RND
    ----------
      19191411
    
    SQL> select f_rnd from dual;
    
         F_RND
    ----------
      16411522
    
    SQL> select * from test where cb_picked = 1;
    
            ID  CB_PICKED
    ---------- ----------
      16411522          1
      19191411          1
    
    SQL>
    

    That's it, I guess.