Clone a table

Posted by

Sometimes doing a CREATE TABLE AS SELECT is all we need to copy the data from an existing table.  But what if we want more than that ?  What if we really want to clone that table to match the original as closely as possible.  We had a question along these lines on AskTOM today.  A standard CTAS copies the NOT NULL attributes and the data types, but not really much else.  We know that Data Pump will take care of it, but that is more complex than a simple CTAS.

So here is a simple routine to wrap the Data Pump calls so that the CTAS can be achieved with just as simple a command.  A database link pointing back to the same database is all we need.

Note:  The true innovation in this blog post came from Laurent’s excellent idea here.  I am just adding a small wrapper to make the process a little more palatable.  So all credit to Laurent here please.


SQL> create table emp as select * from scott.emp;

Table created.

SQL> create sequence seq start with 8000;

Sequence created.

SQL> alter table emp modify empno default seq.nextval;

Table altered.

SQL> alter table emp add primary key ( empno );

Table altered.

SQL> alter table emp add unique ( ename );

Table altered.

SQL> alter table emp compress;

Table altered.

SQL> alter table emp enable row movement;

Table altered.

And here is the routine to clone it, whilst keeping all of those additional bits of metadata.


SQL> create or replace
  2  procedure clone_tab(p_source varchar2,p_target varchar2) is
  3    n number;
  4    g varchar2(30);
  5    j varchar2(30);
  6  begin
  7    select global_name into g from global_name;
  8    begin
  9      execute immediate 'alter session close database link tmp$1';
 10    exception
 11      when others then null;
 12    end;
 13
 14    begin
 15      execute immediate 'drop database link tmp$1';
 16    exception
 17      when others then null;
 18    end;
 19
 20    execute immediate 'create database link tmp$1 using '''||g||'''';
 21
 22    if p_target like '%.%' or p_source like '%.%' then
 23      raise_application_error(-20000,'No schema prefix allowed');
 24    end if;
 25
 26    n := dbms_datapump.open('IMPORT','TABLE','TMP$1');
 27    dbms_datapump.metadata_filter(n,'NAME_LIST',''''||upper(p_source)||'''');
 28    dbms_datapump.metadata_remap(n,'REMAP_TABLE',upper(p_source),upper(p_target));
 29    dbms_datapump.start_job(n);
 30    dbms_datapump.wait_for_job(n, j);
 31  end;
 32  /

Procedure created.

SQL>
SQL> set serverout on
SQL> exec clone_tab('emp','emp2');

PL/SQL procedure successfully completed.

SQL>
SQL> select dbms_metadata.get_ddl('TABLE','EMP2',user) from dual
  2
SQL> select dbms_metadata.get_ddl('TABLE','EMP2',user) from dual;

DBMS_METADATA.GET_DDL('TABLE','EMP2',USER)
---------------------------------------------------------------------------------------

  CREATE TABLE "MCDONAC"."EMP2"
   (    "EMPNO" NUMBER(4,0) DEFAULT "MCDONAC"."SEQ"."NEXTVAL" NOT NULL ENABLE,
        "ENAME" VARCHAR2(10),
        "JOB" VARCHAR2(9),
        "MGR" NUMBER(4,0),
        "HIREDATE" DATE,
        "SAL" NUMBER(7,2),
        "COMM" NUMBER(7,2),
        "DEPTNO" NUMBER(2,0),
         UNIQUE ("ENAME")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "USERS"  ENABLE,
         PRIMARY KEY ("EMPNO")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "USERS"  ENABLE
   ) SEGMENT CREATION IMMEDIATE
  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
 COMPRESS BASIC LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "USERS"  ENABLE ROW MOVEMENT

SQL> select count(*) from emp;

  COUNT(*)
----------
        14

SQL> select count(*) from emp2;

  COUNT(*)
----------
        14

7 comments

    1. Thanks for the link.

      Why are schema prefixes not allowed? Does it mean that we can only clone a table within the *same* schema but not clone it into any other schema?

  1. This is just a simple demo of how you can combine the data pump plsql api with a loopback database link to perform operations on the same database without “leaving” the current session.

    You could extend it via any means you like (add QUERY clause, or the VIEWS_AS_TABLES in 12c) to take advantage of whatever data pump features you desire.

    1. Hi Connor,

      Procedure executed successfully cloning the table, however, the following was observed in my testing:-

      1. Any user-provided “named” constraints like PRIMARY KEY or FOREIGN key did not copy over.

      2. CHECK constraints that start with ‘SYS_%’ did copy over, however, any “named” constraints did not copy over.

      3. Indexes did not copy over.

  2. Hi,
    Connor

    My Oracle DB is 11.2.0.4.0 for windows x64,according to your script,although creating procedure is successful,and when I exec clone_tab(’emp’,’emp2′),show some error as follows,

    SQL> set serveroutput on
    SQL> exec clone_tab(’emp’,’emp2′);
    BEGIN clone_tab(’emp’,’emp2′); END;

    *
    ERROR at line 1:
    ORA-39001: Invalid parameter value
    ORA-06512: In “SYS.DBMS_SYS_ERROR”, line 79
    ORA-06512: In “SYS.DBMS_DATAPUMP”, line 3507
    ORA-06512: In “SYS.DBMS_DATAPUMP”, line 5296
    ORA-06512: In “SYS.CLONE_TAB”, line 25
    ORA-06512: In line 1

    I am confused about above,hope you help me solve,thanks!

    About your creating dblink’s grammar is “create database link tmp$1 using ‘global_name’,and it needn’t to add ‘connect to username by password’ ?

    Best Regards

    Quanwen Zhao

  3. Hello Conor,

    I must admit I was drawn to this post by the word “clone” (but not exactly what I have been looking for…)
    Not sure if you have anything to recommend but I would really love it if there was a way to clone a table, with the data stored in (almost) exact same way as original.
    I keep coming across databases where there are tables that get “fragmented” over time due to data being trickle-fed into those while data also getting removed (I know..I know…you will ask why not use partitioning?). And often I come across some report that accesses such tables, has its performance degraded over time. It would be great if there was a way to clone such table, in order to play with and reproduce the performance issues and either prove/debunk the theory of “fragmentation causing poor performance”.

Got some thoughts? Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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