Exchange/Move a Oracle partition without interim table - oracle

I would like to exchange the Oracle partition to another partitioned table. But to do that we are using interim non partitioned table. Can this be done in a different way?
Can't we exchange a partition from one partitioned table to another without using non-partitioned table as a medium? Can't we directly move a partition to target table?

Easy to put a little API together to make this simple. In this example, T1 and T2 are matching, but T1 and T3 are not
SQL>
SQL> create table t1
2 partition by list ( owner) automatic
3 ( partition px values ('SYS') )
4 as select * from dba_objects;
Table created.
SQL>
SQL> create table t2
2 partition by list ( owner) automatic
3 ( partition py values ('SYS') )
4 as select * from dba_objects;
Table created.
SQL>
SQL> create table t3
2 partition by list ( owner) automatic
3 ( partition pz values ('SYSTEM') )
4 as select * from dba_segments;
Table created.
SQL>
SQL> create sequence seq;
Sequence created.
SQL>
SQL> create or replace
2 procedure exchange_par(
3 p_table1 varchar2,
4 p_table2 varchar2,
5 p_table1_par varchar2,
6 p_table2_par varchar2) is
7 l_tab_cnt int;
8 l_hash_cnt int;
9 l_interim varchar2(100);
10 begin
11 select count(distinct tot), count(*)
12 into l_hash_cnt, l_tab_cnt
13 from
14 ( select table_name, sum(ora_hash(column_name)) tot
15 from user_tab_cols
16 where table_name in (p_table1,p_table2)
17 group by table_name
18 );
19
20 if l_tab_cnt != 2 or l_hash_cnt != 1 then
21 raise_application_error(-20000,'Dead in the water');
22 end if;
23 l_interim := 'TMPTAB$'||seq.nextval;
24
25 begin
26 execute immediate
27 'drop table '||l_interim||' purge';
28 exception
29 when others then
30 if sqlcode = -942 then null; else raise; end if;
31 end;
32
33 execute immediate
34 'create table '||l_interim||' for exchange with table '||p_table1;
35
36 execute immediate
37 'alter table '||p_table1||' exchange partition '||p_table1_par||
38 ' with table '||l_interim;
39
40 execute immediate
41 'alter table '||p_table2||' exchange partition '||p_table2_par||
42 ' with table '||l_interim;
43
44 execute immediate
45 'drop table '||l_interim||' purge';
46
47 end;
48 /
Procedure created.
SQL>
SQL> exec exchange_par('T1','T2','PX','PY');
PL/SQL procedure successfully completed.
SQL>
SQL> exec exchange_par('T1','T3','PX','PZ');
BEGIN exchange_par('T1','T3','PX','PZ'); END;
*
ERROR at line 1:
ORA-20000: Dead in the water
ORA-06512: at "MCDONAC.EXCHANGE_PAR", line 20
ORA-06512: at line 1
SQL>
SQL>
SQL>
SQL>
Can make this as robust as you like with more conditions etc.

Related

How can i execute code faster by using PLSQL

----- My code is taking so much time to execute .it takes 1074 seconds to execute. Can someone tell me any way so that I can execute more faster--------
set serveroutput on;
declare
table_or_view_does_not_exist exception;
pragma exception_init(table_or_view_does_not_exist,-00942);
d_table varchar2(200);
q_table varchar2(200);
r_emp SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
type t_list is table of all_tab_columns%rowtype index by PLS_INTEGER;
v_array t_list;
begin
begin
d_table:='drop table subs_profile_spcl_char PURGE';
execute immediate d_table;
exception
when table_or_view_does_not_exist then
null;
end;
dbms_output.put_line('Table has been dropped');
q_table:='create table subs_profile_spcl_char
(column_name varchar2(50),
spcl_char_count Number)';
execute immediate q_table;
dbms_output.put_line('Table has been created');
dbms_output.enable;
select /*parallel(14)*/ * bulk collect into v_array from all_tab_columns where table_name='SUBSCRIBER_PROFILE' and OWNER='MIG';
for i in 1..v_array.count() loop
r_emp.extend;
EXECUTE IMMEDIATE
'select /*parallel(16)*/ count(*) from '||v_array(i).table_name||' where not regexp_like ('||v_array(i).column_name||',''[A-za-z0-9.]'')'
into r_emp(i);
if r_emp(i)<>0 then
dbms_output.put_line(v_array(i).column_name||'------------>>>>'||r_emp(i));
execute immediate 'insert into subs_profile_spcl_char values (:param1,:param2)' using v_array(i).column_name,r_emp(i);
end if;
end loop;
end;
The way I see it, a few objections.
you shouldn't drop/create table within PL/SQL; that's just bad practice. Create table at SQL level, then remove its contents (if you have to) in a procedure (truncate is faster than delete, but is irreversible. As you actually dropped the table, I'd say that you can use it)
target table is wrongly created anyway; knowing only the column name is far from enough - you should be storing owner and table name as well
it seems that you're looking for columns that contain "special characters" (i.e. not alphanumerics nor dots); in that case, modify regular expression
no need to scan all owners - SYS, SYSTEM, CTXSYS, possible APEX_ users most probably aren't interesting in what you're doing so - remove them. Remove other owners as well, if you want.
no need to scan all columns - numbers and dates can't contain special characters - filter only CHAR datatype family columns
no need to use r_emp collection. Fetch the count into a scalar variable (l_cnt in my example)
parallel "hint" is wrongly used. The way you put it, it is just a comment, not a hint. Hint looks like e.g. select /*+ parallel */ (you're missing a plus sign)
dbms_output takes resources; remove it if you don't need it (you don't; the result is anyway stored into the table)
insert into the target table doesn't require dynamic SQL so - switch to an ordinary insert
OK, here you go, here's what you might try to do.
(re)create the target table at SQL level:
SQL> DROP TABLE subs_profile_spcl_char;
Table dropped.
SQL> CREATE TABLE subs_profile_spcl_char
2 (
3 owner VARCHAR2 (30),
4 table_name VARCHAR2 (30),
5 column_name VARCHAR2 (30),
6 spcl_char_count NUMBER
7 );
Table created.
PL/SQL code:
SQL> set timing on
SQL>
SQL> DECLARE
2 TYPE t_list_rec IS RECORD
3 (
4 owner all_tab_columns.owner%TYPE,
5 table_name all_tab_columns.table_name%TYPE,
6 column_name all_tab_columns.column_name%TYPE
7 );
8
9 TYPE t_list IS TABLE OF t_list_rec;
10
11 v_array t_list;
12 --
13 l_cnt NUMBER;
14 BEGIN
15 EXECUTE IMMEDIATE 'truncate table subs_profile_spcl_char';
16
17 SELECT owner, table_name, column_name
18 BULK COLLECT INTO v_array
19 FROM all_tab_columns
20 WHERE owner NOT LIKE '%SYS%'
21 AND owner NOT LIKE 'APEX%'
22 AND data_type LIKE '%CHAR%';
23
24 FOR i IN 1 .. v_array.COUNT ()
25 LOOP
26 BEGIN
27 EXECUTE IMMEDIATE 'select count(*) from '
28 || v_array (i).owner
29 || '.'
30 || v_array (i).table_name
31 || ' where not regexp_like ('
32 || v_array (i).column_name
33 || ',''^[A-za-z0-9.]+$'')'
34 INTO l_cnt;
35 EXCEPTION
36 WHEN OTHERS
37 THEN
38 -- for tables you can't access
39 NULL;
40 END;
41
42 IF l_cnt > 0
43 THEN
44 INSERT INTO subs_profile_spcl_char
45 VALUES (v_array (i).owner,
46 v_array (i).table_name,
47 v_array (i).column_name,
48 l_cnt);
49 END IF;
50 END LOOP;
51 END;
52 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.58
SQL> set timing off
It took 580 miliseconds (as opposed to your 1000 seconds).
Number of scanned columns:
SQL> SELECT COUNT (*)
2 FROM all_tab_columns
3 WHERE owner NOT LIKE '%SYS%'
4 AND owner NOT LIKE 'APEX%'
5 AND data_type LIKE '%CHAR%';
COUNT(*)
----------
172
Without filter:
SQL> SELECT COUNT (*)
2 FROM all_tab_columns;
COUNT(*)
----------
39697
SQL>
Why would you check 40.000 columns, if you can do that on 200 columns instead?
Final result:
SQL> SELECT *
2 FROM subs_profile_spcl_char
3 ORDER BY owner DESC, table_name, column_name;
OWNER TABLE_NAME COLUMN_NAME SPCL_CHAR_COUNT
-------------------- ------------------------- -------------------- ---------------
XDB PATH_VIEW PATH 137
XDB RESOURCE_VIEW ANY_PATH 137
SCOTT BONUS JOB 1
SCOTT DEPT LOC 1
SCOTT PRODUCTS PRODUCT_NAME 4
SCOTT SUBS_PROFILE_SPCL_CHAR COLUMN_NAME 18
SCOTT SUBS_PROFILE_SPCL_CHAR OWNER 2
SCOTT SUBS_PROFILE_SPCL_CHAR TABLE_NAME 24
ORDS_METADATA USER_ORDS_OBJECTS OBJECT_ALIAS 3
ORDS_METADATA USER_ORDS_OBJECTS PARSING_OBJECT 3
ORDDATA ORDDCM_DBRELEASE_DOCS DOC_TYPE 3
ORDDATA ORDDCM_DOCUMENT_TYPES DOC_TYPE 4
<snip>
29 rows selected.
SQL>

Execute multiple Oracle SQL statements simultaneously

I have an Oracle database that has 20 very large tables, each with hundreds of partitions. I can compress a table but it takes over 2 hours. This would mean over 40 hours to complete all 20 tables. I would like to run the partition compression simultaneously (1 per table). Because the partitions are added daily I need a utility to generate the "alter table ..." syntax at the time of the run. So far, all I can think of is to create a SQL for each of the 20 tables and their partitions, then run them in 20 SQLPlus sessions.
Is there a better, more automated way to do this?
You could submit several jobs, which will - in turn - run your code simultaneously. Here's an example:
Test tables - I want to modify ID's datatype to VARCHAR2(10)
SQL> create table t1 (id varchar2(2));
Table created.
SQL> create table t2 (id varchar2(2));
Table created.
SQL> create table t3 (id varchar2(2));
Table created.
A procedure which will utilize EXECUTE IMMEDIATE and is called from DBMS_JOB (see below):
SQL> create or replace procedure p_exe (par_what in varchar2) is
2 begin
3 execute immediate par_what;
4 end;
5 /
Procedure created.
Create jobs which will run the ALTER TABLE simultaneously
SQL> declare
2 l_str varchar2(200);
3 l_job number;
4 begin
5 for cur_r in (select 't1' table_name from dual union all
6 select 't2' from dual union all
7 select 't3' from dual)
8 loop
9 l_str := 'alter table ' || cur_r.table_name || ' modify id varchar2(10)';
10 dbms_output.put_line(l_str);
11 dbms_job.submit(l_job,
12 'p_exe(' || chr(39) || l_str || chr(39) ||');',
13 sysdate
14 );
15 commit;
16 end loop;
17 end;
18 /
PL/SQL procedure successfully completed.
Jobs are now running; in a few moments (in my case, as it is a simple one - you'd check that somewhat later), check what's being done:
SQL> desc t1;
Name Null? Type
----------------------- -------- ----------------
ID VARCHAR2(10)
SQL> desc t3;
Name Null? Type
----------------------- -------- ----------------
ID VARCHAR2(10)
SQL> desc t3;
Name Null? Type
----------------------- -------- ----------------
ID VARCHAR2(10)
SQL>

Passing DataTable to Oracle Procedure

I want to pass DataTable to Oracle procedure which creates a Temporary table.
Even My procedure needs changes to accept datatable as parameter.
I want datatable in place of existing_table.
Below is the Procedure:
CREATE OR REPLACE procedure temptable
is
begin
EXECUTE IMMEDIATE'CREATE GLOBAL TEMPORARY TABLE TEMP_TABLE
ON COMMIT PRESERVE ROWS
AS
select * from existing_table';
End;
How do I work through this. Please help!
Here's an example of how you might do that:
SQL> create or replace procedure temptable (par_table_name in varchar2)
2 is
3 l_cnt number;
4 l_str varchar2(200);
5 begin
6 -- if TEMP_TABLE already exists, drop it
7 select count(*)
8 into l_cnt
9 from user_tables
10 where table_name = 'TEMP_TABLE';
11
12 if l_cnt > 0 then
13 execute immediate 'drop table temp_table';
14 end if;
15
16 -- create a new TEMP_TABLE
17 l_str := 'create global temporary table temp_table on commit preserve rows ' ||
18 'as select * from ' || par_table_name;
19 execute immediate (l_str);
20 end;
21 /
Procedure created.
SQL> exec temptable('dept');
PL/SQL procedure successfully completed.
SQL> select * from temp_table;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
However, in Oracle, you don't really create/drop tables dynamically - you create them once and use them many times. For a global temporary table, you'd create it once and populate as necessary in different sessions.
There might be requirements as yours (i.e. to use the same table name for different data sources), so - if that's your case, see if the above code helps.

insert record into table on exception

Here is my scenario:
I want to write a procedure in oracle, there are four tables, tab1, tab2, tab3, err. tab1 has some data in two columns (id number, name varchar(250)), while others are empty.
schema for tab2 is
(id number, name varchar(50)), for tab3 (id number, name varchar(250)).
I want to insert data from tab1 to tab2 and when there is exception like name is greater than varchar(50), it will insert into tab3 and also insert error message into err table.
So all record from tab1 should be inserted into tab2 and tab3 accordingly using exception handling.
Here is what I tried but failed.
CREATE OR REPLACE
PROCEDURE exception_handler
IS
vSqlErr VARCHAR2(200) ;
vSqlCode VARCHAR2(5) ;
id2 NUMBER;
name2 VARCHAR(250);
BEGIN
INSERT ALL INTO tab3 VALUES
(id, name
)
SELECT * FROM tab1 t;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO tab2 VALUES
(id, name
);
vSqlErr := SUBSTR(sqlerrm, 1, 200) ;
vSqlCode := SUBSTR(SQLCODE, 1, 5) ;
INSERT INTO err VALUES
(vSqlErr, vSqlCode
) ;
COMMIT ;
RAISE;
END;
This is just a simple demonstration based on your inputs in the question. Better go for BULK processing and SQL%BULK_EXCEPTIONS. And don't use WHEN OTHERS blindly.
Let's say you have an EMP table, and you have a check constraint on employee name as not more than 5 characters. There is an EMP_ERR table to log the error values and error message. Lets see a test case:
SQL> DROP TABLE emp_new PURGE;
Table dropped.
SQL> CREATE TABLE emp_new AS
2 SELECT * FROM emp WHERE 1 =2;
Table created.
SQL> ALTER TABLE emp_new ADD CONSTRAINT check_ename CHECK(LENGTH(ename)<=5);
Table altered.
SQL> DROP TABLE emp_err PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE emp_err
2 (
3 empno NUMBER,
4 ename VARCHAR2(100),
5 err_msg VARCHAR2(250)
6 );
Table created.
SQL> CREATE OR REPLACE
2 PROCEDURE p
3 (
4 v_empno NUMBER,
5 v_ename VARCHAR2
6 )
7 IS
8 vSqlErr VARCHAR2(200) ;
9 vSqlCode VARCHAR2(5) ;
10 empno2 NUMBER;
11 ename2 VARCHAR2(250);
12 BEGIN
13 INSERT INTO emp_new
14 (empno, ename
15 ) VALUES
16 (v_empno, v_ename
17 );
18 COMMIT;
19 EXCEPTION
20 WHEN OTHERS THEN
21 vSqlErr := SUBSTR(sqlerrm, 1, 200) ;
22 vSqlCode := SUBSTR(SQLCODE, 1, 5) ;
23 INSERT
24 INTO emp_err
25 (
26 empno,
27 ename,
28 err_msg
29 )
30 VALUES
31 (
32 v_empno,
33 v_ename,
34 vSqlErr
35 ||' - '
36 ||vSqlCode
37 );
38 COMMIT ;
39 raise;
40 END;
41 /
Procedure created.
Lets execute the procure with ename value as more than 5 characters, so that it raises an error, and we expect a row to be inserted into the emp_err table.
SQL> exec p(1, 'abcdef');
BEGIN p(1, 'abcdef'); END;
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CHECK_ENAME) violated
ORA-06512: at "SCOTT.P", line 38
ORA-06512: at line 1
So, the error is raised. Lets see if it is logged in the error table.
SQL> column ename format a10
SQL> column err_msg format a100
SQL> set linesize 150
SQL> select * from emp_err;
EMPNO ENAME ERR_MSG
---------- ---------- ----------------------------------------------------------------
1 abcdef ORA-02290: check constraint (SCOTT.CHECK_ENAME) violated - -2290
SQL>
We have the error details logged.

How do you create a table with random number of fields in Oracle using PL/SQL?

I need to create a Oracle tables with random number of columns for load testing. I just want to specify number of columns with type NUMBER, number of columns with type VARCHAR2 etc and the fields should be generated automatically. Also, I will be filling up the tables with random data for which I will be using dbms_random.
How can I achieve this?
"I just want to specify number of
columns with type NUMBER, number of
columns with type VARCHAR2 etc and the
fields should be generated
automatically."
The following procedure does just that. Note that it is rather basic; you might want to make it more sophisticated, for example by varying the length of the varchar2 columns:
SQL> create or replace procedure bld_table
2 ( p_tab_name in varchar2
3 , no_of_num_cols in pls_integer
4 , no_of_var_cols in pls_integer
5 , no_of_date_cols in pls_integer
6 )
7 as
8 begin
9 execute immediate 'create table '||p_tab_name||' ('
10 ||' pk_col number not null'
11 ||', constraint '||p_tab_name||'_pk primary key (pk_col) using index)';
12 << numcols >>
13 for i in 1..no_of_num_cols loop
14 execute immediate 'alter table '||p_tab_name||' add '
15 ||' col_n'||trim(to_char(i))||' number';
16 end loop numcols;
17 << varcols >>
18 for i in 1..no_of_var_cols loop
19 execute immediate 'alter table '||p_tab_name||' add '
20 ||' col_v'||trim(to_char(i))||' varchar2(30)';
21 end loop varcols;
22 << datcols >>
23 for i in 1..no_of_date_cols loop
24 execute immediate 'alter table '||p_tab_name||' add '
25 ||' col_d'||trim(to_char(i))||' date';
26 end loop datcols;
27 end bld_table;
28 /
Procedure created.
SQL>
Here it is in action:
SQL> exec bld_table ('T23', 2, 3, 0)
PL/SQL procedure successfully completed.
SQL> desc t23
Name Null? Type
----------------------------------------- -------- ----------------------------
PK_COL NOT NULL NUMBER
COL_N1 NUMBER
COL_N2 NUMBER
COL_V1 VARCHAR2(30 CHAR)
COL_V2 VARCHAR2(30 CHAR)
COL_V3 VARCHAR2(30 CHAR)
SQL>
We can also use dynamic SQL to populate the table with rows of random data.
SQL> create or replace procedure pop_table
2 ( p_tab_name in varchar2
3 , p_no_of_rows in pls_integer
4 )
5 as
6 stmt varchar2(32767);
7 begin
8 stmt := 'insert into '||p_tab_name
9 || ' select rownum ';
10 for r in ( select column_name
11 , data_type
12 , data_length
13 from user_tab_columns
14 where table_name = p_tab_name
15 and column_name != 'PK_COL' )
16 loop
17 case r.data_type
18 when 'VARCHAR2' then
19 stmt := stmt ||', dbms_random.string(''a'', '||r.data_length||')';
20 when 'NUMBER' then
21 stmt := stmt ||', dbms_random.value(0, 1000)';
22 when 'DATE' then
23 stmt := stmt ||', sysdate + dbms_random.value(-1000, 0)';
24 end case;
25 end loop;
26 stmt := stmt || ' from dual connect by level <= '||p_no_of_rows;
27 execute immediate stmt;
28 end pop_table;
29 /
Procedure created.
SQL>
Note that the primary key is populated with the ROWNUM so it will most likely fail if the table already contains rows.
SQL> exec pop_table('T23', 4)
PL/SQL procedure successfully completed.
SQL> select * from t23
2 /
PK_COL COL_N1 COL_N2 COL_V1 COL_V2 COL_V3
---------- ---------- ---------- ------------------------------ ----------------------------- ------------------------------
1 913.797432 934.265814 NUtxjLoRQMCTLNMPKVGbTZwJeYaqnXTkCcWu WFRSHjXdLfpgVYOjzrGrtUoX jIBSoYOhSdhRFeEeFlpAxoanPabvwK
2 346.879815 104.800387 NTkvIlKeJWybCTNEdvsqJOKyidNkjgngwRNN PPIOInbzInrsVTmFYcDvwygr RyKFoMoSiWTmjTqRBCqDxApIIrctPu
3 93.1220275 649.335267 NTUxzPRrKKfFncWaeuzuyWzapmzEGtAwpnjj jHILMWJlcMjnlboOQEIDFTBG JRozyOpWkfmrQJfbiiNaOnSXxIzuHk
4 806.709357 857.489387 ZwLLkyINrVeCkUpznVdTHTdHZnuFzfPJbxCB HnoaErdzIHXlddOPETzzkFQk dXWTTgDsIeasNHSPbAsDRIUEyPILDT
4 rows selected.
SQL>
Again, there are all sorts of ways to improve the sophistication of the data.
As an aside, using these sorts of data pools for load testing is not always a good idea. Performance problems are often caused by skews in the distribution of data values which you just aren't going to get with DBMS_RANDOM. This is particularly true of some date columns - e.g. START_DATE - which would tend to be clustered together in real life but the above procedure will not generate that pattern. Similarly maxing out the varchar2 columns will lead to tables which take up more storage than they wlll under real-life usage.
In short, randomly generated data is better than nothing but we need to understand its weaknesses.
Two approaches
1) Write code to generate text files containing the CREATE TABLE commands that you need to run and populate your tables, then execute these using SQL*Plus.
2) Use Dynamic SQL embedded inside PL/SQL -
PROCEDURE create_random_table
(pTableName IN VARCHAR2,
pNumberOfColumns IN INTEGER)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
lCommand VARCHAR2(32000);
BEGIN
lCommand :=
'CREATE TABLE '||pTableName||'(';
FOR i IN 1..pNumberOfColumns LOOP
append your column definition here
END LOOP;
lCommand := lCommand||';';
--
EXECUTE IMMEDIATE lCommand;
END;
You could also use 'CREATE TABLE AS SELECT' to populate your table at the same time (see other answer).
This should give you a good starting point - there isn't anything in the system that will do what you want without writing code.
You may generate such table by yourself.
Create table with required datatypes:
Create Table RandomTable
AS
Select dbms_random.string('A', 1) CharField,
dbms_random.string('a', 20) VarCharField,
TRUNC(dbms_random.value(0, 35000)) IntField,
dbms_random.value(0, 35000) NumberField,
Level SequenceField,
sysdate + dbms_random.value(-3500, 3500) DateField
from dual connect by level < 1000
(You can change 1000 to required rows numbers, and add required data types)
After that you can create or populate tables with random data from RandomTable
Create Table TestTable1
AS
SELECT CharField as a, NumberField as b
from RandomTable
INSERT INTO TestTable2(DateFrom, Quantity)
SELECT DateField, IntField
from RandomTable
Where SequenceField <= 500

Resources