Since 12c we have had the cool IDENTITY column feature which removes the tedium of creating sequences, triggers and the like to maintain a simple surrogate column (typically a primary key).


SQL> create table t ( x int generated  always as identity , y int);

Table created.

SQL> insert into t (y) values (10);

1 row created.

SQL> select * from t;

         X          Y
---------- ----------
         1         10

Behind the scenes nothing has changed. We are still using a sequence to generate the values but it is just happening “automagically” for us.


SQL> select *
  2  from   user_tab_identity_cols
  3  where  table_name ='T'
  4  @pr
==============================
TABLE_NAME                    : T
COLUMN_NAME                   : X
GENERATION_TYPE               : ALWAYS
SEQUENCE_NAME                 : ISEQ$_254289
IDENTITY_OPTIONS              : START WITH: 1, 
                                INCREMENT BY: 1, 
                                MAX_VALUE: 9999999999999999999999999999, 
                                MIN_VALUE: 1, 
                                CYCLE_FLAG: N, 
                                CACHE_SIZE: 20, 
                                ORDER_FLAG: N, 
                                SCALE_FLAG: N, 
                                EXTEND_FLAG: N, 
                                SESSION_FLAG: N, 
                                KEEP_VALUE: N

PL/SQL procedure successfully completed.         

However, notice that in the INSERT above I omitted the identity column. Because the database is now controlling the column, you do not get to reference it during an insert when defined as above.


SQL> create table t ( x int generated  always as identity , y int);

Table created.

SQL> insert into t values (1,10);
insert into t values (1,10)
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column

This is normally not any cause for concern. However, one place where you might get caught out is using the nifty PL/SQL row clause for DML. With any currently supported version of the Oracle database, PL/SQL allows you to use a shortcut to insert an entire row of data using a %ROWTYPE variable.


SQL> create table t ( x int, y int );

Table created.

SQL> declare
  2    r t%rowtype;
  3  begin
  4    r.x := 1;
  5    r.y := 10;
  6    insert into t values r;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Because the smallest “unit of work” for an insert is a row, this syntax is a nice convenience feature of PL/SQL. This is not a true SQL feature as such. Behind the scenes at execution time the INSERT is expanded out to the all of the required columns and bind variables for each of the %ROWTYPE attributes.

This unfortunately creates a conflict with an IDENTITY column because as we just observed, you are not allowed to reference the column in your INSERT, and hence we get the following error:


SQL> create table t ( x int generated always as identity , y int);

Table created.

SQL> declare
  2    r t%rowtype;
  3  begin
  4    r.y := 10;
  5    insert into t values r;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
ORA-06512: at line 5

The hint here is the “ALWAYS” part of the syntax. As the text suggests, we are always going to generate the value, and as such you are always not allowed to provide it.

Another option for identity columns is “DEFAULT” instead of “ALWAYS”. Using this will indeed allow the column to be now referenced via a %ROWTYPE variable, but unfortunately, because we are explicitly binding each of the individual variables to the INSERT, we’ll get a NOT NULL violation.


SQL> create table t ( x int generated by default as identity, y int);

Table created.

SQL> declare
  2    r t%rowtype;
  3  begin
  4    r.y := 10;
  5    insert into t values r;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("MCDONAC"."T"."X")
ORA-06512: at line 5

Thus if you want to keep the ease and convenience of using %ROWTYPE variables, but you also want to use an identity column, you will need to opt for the DEFAULT ON NULL syntax to ensure you do not get any nasty consequences.


SQL> create table t ( x int generated by default on null as identity , y int);

Table created.

SQL> declare
  2    r t%rowtype;
  3  begin
  4    r.y := 10;
  5    insert into t values r;
  6  end;
  7  /

PL/SQL procedure successfully completed.

12 responses to “PL/SQL and mistaken IDENTITY”

  1. — we redefine tables at lot . identity columns prevent redefs.
    create table t ( x int generated by default on null as identity , y int);

    sql>
    sql>begin
    2 dbms_redefinition.redef_table(
    3 uname => user,
    4 tname => ‘T’,
    5 table_compression_type => ‘COMPRESS’)
    6 ;
    7 end;
    8 /
    begin
    *
    ERROR at line 1:
    ORA-23550: cannot redefine a table with interim table having identity columns that appear in column mapping
    ORA-06512: at “SYS.DBMS_REDEFINITION”, line 5678
    ORA-06512: at “SYS.DBMS_REDEFINITION”, line 113
    ORA-06512: at “SYS.DBMS_REDEFINITION”, line 4363
    ORA-06512: at “SYS.DBMS_REDEFINITION”, line 5426
    ORA-06512: at “SYS.DBMS_REDEFINITION”, line 5713
    ORA-06512: at line 2

    sql>

    So we go with manually created sequence and default value of the sequence, which does allow redefs. Be nice enhancement if redef could support identity cols though.

    If using default value for sequence remember to grant select on sequence if adding any additional insert grants.

    1. Yeah I agree its a limitation.

      You can “hack” around it with

      – interim table does NOT have identity
      – do the redefine
      – add a NEW identity column
      – set the old column to unused
      – rename the new column to replace the old one

      But that’s complex, prone to error and increases the unavailability

  2. aminaa49ed2e27e Avatar
    aminaa49ed2e27e

    Suppose I have a hundred Tables. Each table will have its own sequence. Is there a way to specify a fewer number if sequences to be shared by the Tables?

    1. There isn’t a (practical) limit on the number of objects in a database so I would not worry about it.

      If you (really) want less sequences, then you would revert to “col default mysequence.nextval”

  3. aminaa49ed2e27e Avatar
    aminaa49ed2e27e

    Suppose I have a 100 Tables. Each will have a sequence. Is the a way to share a sequence among the Tables?

    Regards

  4. Hi,

    I did a random case with X as default on null, and this is what I could see.4

    X Y
    1 10 — Automatically populate X
    2 10 — Automatically populate X
    5 11 — Hardcode the x value
    4 12 — Automatically populate X
    5 13 — Automatically populate X

    1 14 — Hardcode the x value

    7 15 — Automatically populate X
    8 16 — Automatically populate X
    9 17 — Automatically populate X

    So with this option, we can manually change the sequence of X to what ever we want, but eventually it skips the sequence if there is a manual intervention, and continues as if it was like a sequence.nextval

    is my understanding correct?

    Thanks,

    Rayan

  5. Correct – that is the “price” of falling back to ‘default on null’

  6. Though at first blush this has always seemed a cool feature, your article has caused me to rethink that.

    The limitations of using an identity column may not be worth it, depending on application.

    The ‘myrec mytab%rowtype’ conundrum could be mitigated by using a function to build the rowtype and return it, but that does mean the simple declaration becomes declaration, code to return the record, and assigning the value ‘myrec’ in the code rather than the declaration section.

    The old standby of trigger+sequence looks even better. Both of the trigger and sequence are easily generated anyway (gee, we did that 30 years ago), so not really much of a bother.

    1. I am somewhere in between – I prefer sequence plus “default seq.nextval” on the column, because one less trigger is always a good thing.

      1. I think you’re right, ‘default seq.nextval’ seems a better solution. One less object, and it may perform better than the trigger, though I haven’t tested that.
        IAC, I had forgotten all about ‘default seq.nextval’

  7. I like “generated by default as identity” for another reason: ability to copy data from one database to another while keeping the same primary keys. Follow by “alter table modify generated as identity start with limit value;”.

    To use %ROWTYPE, if the procedure is not supposed to override the default I would consider creating a view without the identity column and allowing write access only to the view.

Got some thoughts? Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Trending