Thursday, August 17, 2017

IDENTITY columns - A deep dive analysis

 
Identity columns were introduced in Oracle 12c, this new feature allows you to specify that a column should be automatically populated from a system created sequence.
First - an example of a table without an identity column.
demo@ORA12C> create table transaction1(
  2     transaction_id int,
  3     holding_id int,
  4     transaction_type number,
  5     transaction_date date,
  6     credit_value number,
  7     debit_value number );
 
Table created.
 
demo@ORA12C> desc transaction1;
 Name                                Null?    Type
 ----------------------------------- -------- -----------
 TRANSACTION_ID                               NUMBER(38)
 HOLDING_ID                                   NUMBER(38)
 TRANSACTION_TYPE                             NUMBER
 TRANSACTION_DATE                             DATE
 CREDIT_VALUE                                 NUMBER
 DEBIT_VALUE                                  NUMBER
 
demo@ORA12C>
 
 
In its simples form, the Identity clause can be specified like this.
 
demo@ORA12C> create table transaction2(
  2     transaction_id int GENERATED AS IDENTITY,
  3     holding_id int,
  4     transaction_type number,
  5     transaction_date date,
  6     credit_value number,
  7     debit_value number );
 
Table created.
 
demo@ORA12C>
demo@ORA12C> desc transaction2;
 Name                                Null?    Type
 ----------------------------------- -------- -------------
 TRANSACTION_ID                      NOT NULL NUMBER(38)
 HOLDING_ID                                   NUMBER(38)
 TRANSACTION_TYPE                             NUMBER
 TRANSACTION_DATE                             DATE
 CREDIT_VALUE                                 NUMBER
 DEBIT_VALUE                                  NUMBER
 
demo@ORA12C>
 
 
Note that a NOT NUL constraint has been automatically created for the TRANSACTION_ID column.
A sequence is created with a system created name ISEQ$$_<object_id>, where object_id is the object_id of the table.
demo@ORA12C> select object_id
  2  from user_objects
  3  where object_name ='TRANSACTION2' ;
 
 OBJECT_ID
----------
     87436
 
demo@ORA12C>
demo@ORA12C> select SEQUENCE_NAME from user_sequences ;
 
SEQUENCE_NAME
---------------
ISEQ$$_87436
 
demo@ORA12C>
 
 
By default, the GENERATED AS IDENTITY clause implicitly include the ALWAYS keyword, i.e GENERATED ALWAYS AS IDENTITY.  When the ALWAYS keyword is specified it is not possible to explicitly include values for the identity column in INSERT or UPDATE statement, doing so will produce error like this.
 
demo@ORA12C> insert into transaction2(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(55,1,0,sysdate,0,2);
insert into transaction2(transaction_id,holding_id,
                         *
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
 
 
Values can also be generated automatically if no values are specified explicitly, using the following syntax.
 
 
demo@ORA12C> create table transaction3(
  2     transaction_id int GENERATED BY DEFAULT AS IDENTITY,
  3     holding_id int,
  4     transaction_type number,
  5     transaction_date date,
  6     credit_value number,
  7     debit_value number );
 
Table created.
 
 
Unlike the GENERATED AS IDENTITY clause, using GENERATED BY DEFAULT AS IDENTITY we can insert the transaction_id column explicitly like this.
 
 
demo@ORA12C> insert into transaction3(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(55,1,0,sysdate,0,2);
 
1 row created.
 
 
Or we could ignore the transaction_id column, where the database would take care of populating the values into this IDENTITY column.
 
 
demo@ORA12C> insert into transaction3(holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(1,0,sysdate,0,2);
 
1 row created.
demo@ORA12C> select * from transaction3 ;
 
TRANSACTION_ID HOLDING_ID TRANSACTION_TYPE TRANSACTION CREDIT_VALUE DEBIT_VALUE
-------------- ---------- ---------------- ----------- ------------ -----------
            55          1                0 31-JUL-2017            0           2
             1          1                0 31-JUL-2017            0           2
 
demo@ORA12C>
 
 
If you wish values to be generated when NULL value is supplied use GENERATED BY DEFAULT AS IDENTITY ON NULL option.
 
demo@ORA12C> create table transaction4(
  2     transaction_id int GENERATED BY DEFAULT ON NULL AS IDENTITY,
  3     holding_id int,
  4     transaction_type number,
  5     transaction_date date,
  6     credit_value number,
  7     debit_value number );
 
Table created.
 
 
With that, we can explicitly insert the values like this.
 
 
demo@ORA12C> insert into transaction4(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(55,1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> select * from transaction4;
 
TRANSACTION_ID HOLDING_ID TRANSACTION_TYPE TRANSACTION CREDIT_VALUE DEBIT_VALUE
-------------- ---------- ---------------- ----------- ------------ -----------
            55          1                0 31-JUL-2017            0           2
 
 
Else have an explicit NULL, and then the database will manipulate them for IDENTITY columns.
 
 
demo@ORA12C> insert into transaction4(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(NULL,1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> select * from transaction4;
 
TRANSACTION_ID HOLDING_ID TRANSACTION_TYPE TRANSACTION CREDIT_VALUE DEBIT_VALUE
-------------- ---------- ---------------- ----------- ------------ -----------
            55          1                0 31-JUL-2017            0           2
             1          1                0 31-JUL-2017            0           2
 
Else have an implicit NULL, and then the database will manipulate them for IDENTITY columns.
 
demo@ORA12C> insert into transaction4(holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> select * from transaction4;
 
TRANSACTION_ID HOLDING_ID TRANSACTION_TYPE TRANSACTION CREDIT_VALUE DEBIT_VALUE
-------------- ---------- ---------------- ----------- ------------ -----------
            55          1                0 31-JUL-2017            0           2
             1          1                0 31-JUL-2017            0           2
             2          1                0 31-JUL-2017            0           2
 
demo@ORA12C>
 
 
We can check which tables have IDENTITY columns from user_tables data dictionary.
 
demo@ORA12C> select table_name ,has_identity
  2  from user_tables
  3  where table_name like 'TRANSACTIO%'
  4  order by 1;
 
TABLE_NAME      HAS
--------------- ---
TRANSACTION1    NO
TRANSACTION2    YES
TRANSACTION3    YES
TRANSACTION4    YES
 
demo@ORA12C>
 
 
The ALTER Table statement supports the IDENTITY clause, but you cannot convert a non-identity column to an identity column.
 
Converting a non-identity column to identity column will return this error.
 
demo@ORA12C> alter table transaction1
  2  modify transaction_id GENERATED AS IDENTITY  ;
modify transaction_id GENERATED AS IDENTITY
       *
ERROR at line 2:
ORA-30673: column to be modified is not an identity column
 
 
Using ALTER Table statement, you can modify BY DEFAULT ON NULL clause into an ALWAYS clause.
 
demo@ORA12C> create table transaction5(
  2     transaction_id int GENERATED BY DEFAULT ON NULL AS IDENTITY primary key,
  3     holding_id int,
  4     transaction_type number,
  5     transaction_date date,
  6     credit_value number,
  7     debit_value number );
 
Table created.
 
demo@ORA12C> insert into transaction5(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(1,1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> insert into transaction5(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(2,1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> insert into transaction5(transaction_id,holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(3,1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> alter table transaction5 modify transaction_id
  2  GENERATED ALWAYS AS IDENTITY (start with limit value) ;
 
Table altered.
 
demo@ORA12C> insert into transaction5(holding_id,
  2     transaction_type,transaction_date,credit_value,debit_value)
  3     values(1,0,sysdate,0,2);
 
1 row created.
 
demo@ORA12C> select * from transaction5 ;
 
TRANSACTION_ID HOLDING_ID TRANSACTION_TYPE TRANSACTION CREDIT_VALUE DEBIT_VALUE
-------------- ---------- ---------------- ----------- ------------ -----------
             1          1                0 31-JUL-2017            0           2
             2          1                0 31-JUL-2017            0           2
             3          1                0 31-JUL-2017            0           2
             4          1                0 31-JUL-2017            0           2
 
demo@ORA12C>
 
START WITH LIMIT VALUE, can only be used with ALTER TABLE MODIFY. If you specify START WITH LIMIT VALUE, then Oracle Database locks the table and finds the maximum identity column value in the table and assigns the value as the sequence generator's high water mark.
 
While we cannot change a non-identity column into an identity column, we can convert an identity column into a non- identity column.
 
demo@ORA12C> alter table transaction5
  2  modify transaction_id
  3  drop identity ;
 
Table altered.
 
demo@ORA12C> select table_name ,has_identity
  2  from user_tables
  3  where table_name ='TRANSACTION5';
 
TABLE_NAME      HAS
--------------- ---
TRANSACTION5    NO
 
demo@ORA12C>
 
 
A Table cannot have more than one IDENTITY column.
 
demo@ORA12C> alter table transaction2
  2  add holding_id2 int
  3  generated always as identity;
add holding_id2 int
    *
ERROR at line 2:
ORA-30669: table can have only one identity column
 
IDENTITY column cannot be possible on non-numeric datatypes.
 
demo@ORA12C> alter table transaction
  2  add holding_id2 varchar2(10)
  3  generated always as identity;
generated always as identity
                           *
ERROR at line 3:
ORA-30675: identity column must be a numeric type
 
IDENTITY column cannot have Default values.
 
demo@ORA12C> alter table transaction2
  2  modify transaction_id default 55 ;
modify transaction_id default 55
       *
ERROR at line 2:
ORA-30674: identity column cannot have a default value
 
 
Create table as select does not inherit the IDENTITY property of a column in the source table.
 
demo@ORA12C> create table transaction6
  2  as
  3  select *
  4  from transaction2;
 
Table created.
 
demo@ORA12C> select table_name ,has_identity
  2  from user_tables
  3  where table_name ='TRANSACTION6';
 
TABLE_NAME      HAS
--------------- ---
TRANSACTION6    NO
 
demo@ORA12C>
 
 
When the table is dropped, the system created sequences will be dropped.
 
demo@ORA12C> select table_name ,has_identity,
  2     (select sequence_name
  3       from user_sequences s ,
  4                user_objects o
  5       where o.object_name = t.table_name
  6       and s.sequence_name like '%'||o.object_id) as sequence_name
  7  from user_tables t
  8  where has_identity = 'YES';
 
TABLE_NAME      HAS SEQUENCE_NAME
--------------- --- ---------------
TRANSACTION3    YES ISEQ$$_87440
TRANSACTION2    YES ISEQ$$_87438
TRANSACTION4    YES ISEQ$$_87442
 
demo@ORA12C> drop table transaction2 purge;
 
Table dropped.
 
demo@ORA12C> drop table transaction3 purge;
 
Table dropped.
 
demo@ORA12C> drop table transaction4 purge;
 
Table dropped.
 
demo@ORA12C> select table_name ,has_identity,
  2     (select sequence_name
  3       from user_sequences s ,
  4                user_objects o
  5       where o.object_name = t.table_name
  6       and s.sequence_name like '%'||o.object_id) as sequence_name
  7  from user_tables t
  8  where has_identity = 'YES';
 
no rows selected
 
demo@ORA12C>