Search code examples
oracle-databaseoracle12cdatabase-sequence

How to implement multidimensional sequences


For example, here is a yearly sequence. The no increments with year:

| no | year |
+----+------+
|  1 | 2016 |
|  2 | 2016 |
|  3 | 2016 |
|  1 | 2017 |
|  2 | 2017 |
|  4 | 2016 |

For now I have created sequence for each year
but the problem is Oracle will not automatically create new sequence in next year.

Another problem is if I want to use a 3D sequence, incrementing within year and type:

| no | year | type |
+----+------+------+
|  1 | 2016 |    a |
|  2 | 2016 |    a |
|  1 | 2016 |    b |
|  1 | 2017 |    b |
|  2 | 2017 |    b |
|  1 | 2017 |    c |

This will be too many sequences in database

I do not recommend max(no) because of parallel access issue. I tried to lock table before getting max(no) in a trigger but it resulted in deadlock.


Solution

  • The only way to do this is with a code control table ...

    create table code_control
        (year number(4,0) not null
         , type varchar2(1) not null
         , last_number number(38,0) default 1 not null
         , primary key (year,type)
        )
    organization index
    /   
    

    ... which is maintained like this ...

    create or replace function get_next_number
        (p_year in number, p_type in varchar2)
        return number
    is
        pragma autonomous_transaction;
        cursor cur_cc is
            select last_number + 1
            from code_control cc
            where cc.year= p_year
            and cc.type = p_type
            for update of last_number;
        next_number number;
    begin
        open cur_cc;
        fetch cur_cc into next_number;
        if cur_cc%found then
            update code_control
            set last_number = next_number
            where current of cur_cc;
        else
            insert into code_control (year,type)
            values (p_year, p_type)
            returning last_number into next_number;
        end if;    
        commit;
        return next_number;
    end;
    /
    

    The important thing is the SELECT ... FOR UPDATE. Pessimistic locking guarantees uniqueness in a multi-user environment. The PRAGMA ensures that maintaining code_control doesn't pollute the broader transaction. It allows us to call the function in a trigger without deadlocks.

    Here is a table with a key like yours:

    create table t42
         (year number(4,0) not null
         , type varchar2(1) not null
         , id number(38,0) 
         , primary key (year,type, id)
    )
    /
    create or replace trigger t42_trg
        before insert on t42 for each row
    begin
        :new.id := get_next_number(:new.year, :new.type);
    end;
    /
    

    There's nothing up my sleeves before I populate t42:

    SQL> select * from code_control;
    
    no rows selected
    
    SQL> select * from t42;
    
    no rows selected
    
    SQL> insert into t42 (year, type) values (2016, 'A');
    
    1 row created.
    
    SQL> insert into t42 (year, type) values (2016, 'A');
    
    1 row created.
    
    SQL> insert into t42 (year, type) values (2016, 'A');
    
    1 row created.
    
    SQL> insert into t42 (year, type) values (2016, 'B');
    
    1 row created.
    
    SQL> insert into t42 (year, type) values (2016, 'A');
    
    1 row created.
    
    SQL> insert into t42 (year, type) values (2017, 'A');
    
    1 row created.
    
    SQL> select * from t42;
    
          YEAR T         ID
    ---------- - ----------
          2016 A          1
          2016 A          2
          2016 A          3
          2016 A          4
          2016 B          1
          2017 A          1
    
    6 rows selected.
    
    SQL> select * from code_control;
    
          YEAR T LAST_NUMBER
    ---------- - -----------
          2016 A           4
          2016 B           1
          2017 A           1
    
    SQL> 
    

    So the obvious objection to this implementation is scalability. Inserting transactions are serialized on the code_control table. That's absolutely true. However the lock is held for the shortest possible time, so this should not be an issue even if the t42 table is populated many times a second.

    However, if the table is subjected to massive numbers of concurrent inserts the locking may become an issue. It is crucial the table has sufficient Interested Transaction slots (INITRANS, MAXTRANS) to cope with concurrent demands. But very busy systems may need a smarter implementation (perhaps generating the IDs in batches); otherwise abandon the compound key in favour of a sequence (because sequences do scale in multi-user environments).