Tuesday, March 28, 2017

How to redefine a table that is created with an identity column

You have the following non-partitioned table defined:

CREATE TABLE MYTABLE
(
  ENTRY_SEQ_NUM        NUMBER Generated as Identity ( START WITH 1180965539 MAXVALUE 9999999999999999999999999999 MINVALUE 1 NOCYCLE CACHE 20 NOORDER NOKEEP) NOT NULL,
  ENTRY_ID             VARCHAR2(50 BYTE)            NULL,
  DOC_NAME             VARCHAR2(100 BYTE)           NULL,
  CREATED              TIMESTAMP(6)                 NULL,
  REVISION#            INTEGER                      NULL,
  APPLICABLE_YEAR      INTEGER                      NULL,
  PERIOD               VARCHAR2(10 BYTE)            NULL,
  DOCUMENT_STATUS      VARCHAR2(50 BYTE)            NULL,
  PHONE#               VARCHAR2(11 BYTE)            NULL
);

The table is growing, and your customer want it partitioned.

Solution:

Create an interim table. Make sure to use a datatype of NUMBER for the column ENTRY_SEQ_NUM, instead of the Identity column:
CREATE TABLE MYTABLE_INTERIM
(
  ENTRY_SEQ_NUM        NUMBER,
  ENTRY_ID             VARCHAR2(50 BYTE),
  DOC_NAME             VARCHAR2(100 BYTE)           NULL,
  CREATED              TIMESTAMP(6)                 NULL,
  REVISION#            INTEGER                      NULL,
  APPLICABLE_YEAR      INTEGER                      NULL,
  PERIOD               VARCHAR2(10 BYTE)            NULL,
  DOCUMENT_STATUS      VARCHAR2(50 BYTE)            NULL,
  PHONE#               VARCHAR2(11 BYTE)            NULL
)
PARTITION BY RANGE (PERIOD)
INTERVAL( NUMTOYMINTERVAL(1,'MONTH'))
(
  PARTITION P_INIT VALUES LESS THAN (TO_DATE('2013-01', 'YYYY-MM'))
)
TABLESPACE USERS
;

Start redefinition. Note that you need to use the TO_DATE function on the partition key column, together with proper masking of the date format string:

begin
DBMS_REDEFINITION.start_redef_table(uname=>'SCOTT',
orig_table=>'MYTABLE',
int_table=>'MYTABLE_INTERIM',
col_mapping =>'ENTRY_SEQ_NUM ENTRY_SEQ_NUM,ENTRY_ID ENTRY_ID,DOC_NAME DOC_NAME,CREATED CREATED,REVSION# REVSION#,APPLICABLE_YEAR APPLICABLE_YEAR,TO_DATE(PERIOD,''YYYY-MM'') PERIOD,DOCUMENT_STATUS DOCUMENT_STATUS,PHONE# PHONE#',
options_flag=>dbms_redefinition.cons_use_pk);
end;
/
Finish the redefinition:
begin
 DBMS_REDEFINITION.FINISH_REDEF_TABLE(uname=>'SCOTT',orig_table=>'MYTABLE',int_table=>'MYTABLE_INTERIM);
end;
/

Add a new column, of type "Identity":
alter session force parallel ddl;
alter session force parallel dml;
alter table mytable
add ENTRY_SEQ_NUM_X NUMBER Generated as Identity ( START WITH 1
                                                   MAXVALUE 9999999999999999999999999999 MINVALUE 1 NOCYCLE CACHE 20
                                                   NOORDER NOKEEP);

Drop the old column:
alter table mytable drop column ENTRY_SEQ_NUM;

Rename the Identity column back to the name of the column you just dropped:
alter table mytable rename column ENTRY_SEQ_NUM_X to ENTRY_SEQ_NUM;

3 comments:

  1. Thank you for the detail explanation. I got the idea from you but have one suggestion to make in order to get the column in the same order. I created the table with identity and while table redefinition I used column_mapping and did not map the identity column. However it creates with NULL constraint. In copy_table_dependents I used ignore_errors=true, it worked fine.

    ReplyDelete
  2. Unless I'm misunderstanding something this process does not retain the previous identity values which could very well be necessary. Here's what I did and please correct me if you think I'll run into trouble. The primary purpose for me needing this was to partition by reference which doesn't seem to be possible as an alter.

    1. I copied the table definition. I included all constraints on the table.
    2. I renamed the identity column by adding a _new to the end of it.
    3. I added a column below the _new column with the old identity column name and declared it as number.
    4. DBMS_REDEFINITION.START_REDEF_TABLE seemed to copy fine without me having to call out columns (using CONS_USE_ROWID).
    5. When calling dbms_redefinition.copy_table_dependents I set copy_constraints to FALSE since I had already set them on the new table.
    6. After dbms_redefinition.finish_redef_table I ran an update on the table to set the new identity column equal to the old identity column.
    I'm using GENERATED BY DEFAULT AS IDENTITY on the identity column so the copy was allowed, otherwise this would have required a few more steps.
    7. I then dropped the column with the old name and then renamed the new identity column to the new name.

    Everything seemed to work fine.

    ReplyDelete
  3. Sorry but what is the point of redefinition if I'm going to alter table add column agian !

    ReplyDelete