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
What is “global_name” in the code? Is it a custom table you created somewhere prior to store what value?
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/refrn/GLOBAL_NAME.html
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?
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.
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.
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
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”.