Oracle renaming auto generated list paritions - oracle

I have several environments where automatic list partitioning is implemented.
Some product managers don't like the system GENERATED partition name, which is being GENERATED and want to reference the partition by a more meaningful name.
In an effort to address those concerns I'm trying to create a procedure, which will rename the partition based on the high_value. In the example below I am partitioning by state so the names I want to generate are P_OHIO, P_IOWA..
In the procedure below I am getting the following error:
ORA-14048: a partition maintenance operation may not be combined with other operations ORA-06512:
Can someone please advise me on why I am getting this error and how to rectify it.
Thanks in advance for your help and expertise.
CREATE TABLE T21
(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
num NUMBER(*,0),
state VARCHAR2(20)
)
SEGMENT CREATION DEFERRED
PARTITION BY LIST (state) AUTOMATIC
(PARTITION P_CALIFORNIA VALUES ('CALIFORNIA')
);
insert into t21 (num, state)
select
level * round(dbms_random.value(1,50)),
case round(dbms_random.value(1,50))
when 1 then 'Alabama'
when 2 then 'Alaska'
when 3 then 'Arizona'
when 4 then 'Arkansas'
when 5 then 'California'
when 6 then 'Colorado'
when 7 then 'Connecticut'
when 8 then 'Delaware'
when 9 then 'Florida'
when 10 then 'Georgia'
when 11 then 'Hawaii'
when 12 then 'Idaho'
when 13 then 'Illinois'
when 14 then 'Indiana'
when 15 then 'Iowa'
when 16 then 'Kansas'
when 17 then 'Kentucky'
when 18 then 'Louisiana'
when 19 then 'Maine'
when 20 then 'Maryland'
when 21 then 'Massachusetts'
when 22 then 'Michigan'
when 23 then 'Minnesota'
when 24 then 'Mississippi'
when 25 then 'Missouri'
when 26 then 'Montana'
when 27 then 'Nebraska'
when 28 then 'Nevada'
when 29 then 'New Hampshire'
when 30 then 'New Jersey'
when 31 then 'New Mexico'
when 32 then 'New York'
when 33 then 'North Carolina'
when 34 then 'North Dakota'
when 35 then 'Ohio'
when 36 then 'Oklahoma'
when 37 then 'Oregon'
when 38 then 'Pennsylvania'
when 39 then 'Rhode Island'
when 40 then 'South Carolina'
when 41 then 'South Dakota'
when 42 then 'Tennessee'
when 43 then 'Texas'
when 44 then 'Utah'
when 45 then 'Vermont'
when 46 then 'Virginia'
when 47 then 'Washington'
when 48 then 'West Virginia'
when 49 then 'Wisconsin'
when 50 then 'Wyoming'
end
from dual
connect by level <= 100;
SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE FROM USER_TAB_PARTITIONS WHERE TABLE_NAME ='T21';
CREATE OR REPLACE PROCEDURE RenameListPartitions
IS
CURSOR PartTables IS
SELECT TABLE_NAME
FROM USER_PART_TABLES
WHERE PARTITIONING_TYPE = 'LIST'
ORDER BY TABLE_NAME;
CURSOR TabParts(aTableName VARCHAR2) IS
SELECT PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE regexp_like(partition_name,'^SYS_P[[:digit:]]{1,10}') AND
TABLE_NAME = aTableName AND
table_name not like 'BIN$%'
ORDER BY PARTITION_POSITION;
newPartName VARCHAR2(30);
BEGIN
FOR aTab IN PartTables LOOP
FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP
execute immediate 'select ' || aPart.HIGH_VALUE || ' from dual' into newPartName;
IF newPartName <> aPart.PARTITION_NAME THEN
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newPartName;
END IF;
END LOOP;
END LOOP;
END RenameListPartitions;
/
EXEC RenameListPartitions;

Some of your partition values have spaces - like 'New York' - so you need to quote the identifiers for the partitions:
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME
||' RENAME PARTITION '||aPart.PARTITION_NAME
||' TO "'||newPartName||'"';
db<>fiddle
Of course, that means you also need to quote them when you refer to them explicitly in the future; it might be better to, say, replace spaces with underscores and change to uppercase, so you get NEW_YORK:
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME
||' RENAME PARTITION '||aPart.PARTITION_NAME
||' TO '||upper(replace(newPartName,' ','_'));
db<>fiddle

Related

Exchange/Move a Oracle partition without interim table

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.

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>

E-mail result of 'select' statement having multiple rows as result set in ORACLE

I am new to ORACLE PL/SQL world. I am trying to figure out a way to calculate something as below.
Let's say you have a MASTER_TABLE as below :
SELECT * FROM MASTER_TABLE;
+----------+----------+------------------+-----------------------+
| SCHEMA | TABLE_NM | REQUIRED_COLUMNS | TABLE_FILTER |
+----------+----------+------------------+-----------------------+
| USER_SCH | A | A1,A2,A3 | EXAM_DT > SYSDATE - 1 |
| USER_SCH | B | B1,B2 | TRUNC(SYSDATE) |
+----------+----------+------------------+-----------------------+
I would like to generate SELECT query from above table such as below:
SELECT 'SELECT SCHEMA || '.' || TABLE_NM ||' WHERE '|| TABLE_FILTER FROM MASTER_TABLE;
Obviously, the result of above query would generate multiple select statements.
Now, I want to execute all such SELECT statements and send out the resultset via e-mail.
The tricky part is, the columns mentioned in the MASTER_TABLE varies (i.e. For table 'A' there can be 3 REQUIRED_COLUMNS to be selected, For table 'B' there can be 2 REQUIRED_COLUMNS to be selected - As shown in the MASTER_TABLE)
I have the e-mail utility ready which basically takes an argument as your_message and sends it out via e-mail.
Here is what I have tried :
Created CURSOR to generate such select statements.
Tried inserting the resultset (LIST OF SELECT QUERIES) to another temp table by concatenating the columns.
(i.e.
SELECT
'SELECT '
|| replace(required_columns, ',', '||'',''||')
|| ' AS MSG_BDY'
|| ' FROM '
|| schema
|| '.'
|| table_nm
|| ' WHERE '
|| table_filter
as my_select_stmt
FROM
master_table;
I am stuck after this.
Can you please help me out ? or is there any approach to achieve this ?.
Note : Tables mentioned in MASTER_TABLE can have 1 or more rows.
I don't have your tables so I used Scott's.
Master table:
SQL> select * From master_table;
SCHEM TABL REQUIRED_COLUMN TABLE_FILTER
----- ---- --------------- ----------------------
scott emp ename, job, sal hiredate < sysdate - 1
scott dept dname, loc deptno = 20
SQL>
Procedure which simulates your mailing procedure; I'll just display those values.
SQL> CREATE OR REPLACE PROCEDURE p_mail (par_result IN SYS.odcivarchar2list)
2 AS
3 BEGIN
4 FOR i IN par_result.FIRST .. par_result.LAST
5 LOOP
6 DBMS_OUTPUT.put_line (par_result (i));
7 END LOOP;
8 END;
9 /
Procedure created.
SQL>
Procedure you actually need; as you composed the select statement(s), now you only have to run them. In order to do so, use dynamic SQL (e.g. execute immediate):
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || REPLACE (required_columns, ',', '||'',''||')
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
SMITH,CLERK,920
ALLEN,SALESMAN,1600
WARD,SALESMAN,1250
JONES,MANAGER,2975
MARTIN,SALESMAN,1250
BLAKE,MANAGER,2850
CLARK,MANAGER,2450
SCOTT,ANALYST,3000
KING,PRESIDENT,10000
TURNER,SALESMAN,1500
ADAMS,CLERK,1300
JAMES,CLERK,950
FORD,ANALYST,3000
MILLER,CLERK,1300
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>
[EDIT: what if you wanted to display 'null' for missing values?]
Well, that's a new moment - probably not very simple. See if this helps.
In order to help myself, I modified master_table and added ID column to uniquely identify every row. It'll be used to split required columns' list to rows, apply NVL to them, apply CAST to columns (because NVL complains if datatypes don't match), aggregate them back using listagg. As this is quite a lot to do, I'm going to create a view and use it instead of the table itself.
SQL> CREATE OR REPLACE VIEW v_master_table
2 AS
3 SELECT id,
4 schema,
5 table_nm,
6 LISTAGG ('NVL(cast(' || col || ' as varchar2(20)), ''null'')', '||'',''||')
7 WITHIN GROUP (ORDER BY lvl)
8 required_columns,
9 table_filter
10 FROM (SELECT id,
11 schema,
12 table_nm,
13 table_filter,
14 COLUMN_VALUE lvl,
15 TRIM (REGEXP_SUBSTR (required_columns,
16 '[^,]+',
17 1,
18 COLUMN_VALUE))
19 col
20 FROM master_table
21 CROSS JOIN
22 TABLE (
23 CAST (
24 MULTISET (
25 SELECT LEVEL
26 FROM DUAL
27 CONNECT BY LEVEL <=
28 REGEXP_COUNT (required_columns,
29 ',')
30 + 1) AS SYS.odcinumberlist)))
31 GROUP BY id,
32 schema,
33 table_nm,
34 table_filter;
View created.
For example, it now looks like this:
SQL> select * from v_master_table where id = 2;
ID SCHEM TABL REQUIRED_COLUMNS TABLE_FILTER
--- ----- ---- ------------------------------------------------------------------------------------- ------------
2 scott dept NVL(cast(dname as varchar2(20)), 'null')||','||NVL(cast(loc as varchar2(20)), 'null') deptno = 20
SQL>
The mailing procedure remains the same, no change.
Anonymous PL/SQL block is slightly changed - I removed REPLACE you previously used as view does it now; also, source is the view, not the table.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || required_columns
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM v_master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
CLARK,09.06.1981,null
KING,17.11.1981,null
MILLER,23.01.1982,null
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>

pl sql replace multiple values in sequence

I have 2 columns in a table named Query and Values.
Query :
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values (l_Name,:l_Line,:l_Type,:l_Command,:l_Rule,:l_Client_ID,:l_Site_ID,l_Name)
Values : #1(2):20 #2(1):H #3(2):IF #4(27):FA - RETAIN OLD MASTER DATA #5(0): #6(0):
CName and SName use same value Manu.
I want to replace binded vars with values in next columns to get executable query.
What i want:
I want to write a procedure which get values from both columns of table to make a query and execute that query.
insert into address_MP (CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values ('Manu','20','H','IF','FA - RETAIN OLD MASTER DATA','','Manu')
Here's one option, which converts both query VALUES clause, as well as values themselves into rows - each in its own cursor loop, pairing bind variable and its value via their row number. CHR(39) is a single quote.
As the table most probably doesn't contain a single row (is it identified by some ID? You never told us), you'll have to adjust it, otherwise it won't work properly.
Test table:
SQL> select * from test;
QUERY
--------------------------------------------------------------------------------
C_VALUES
--------------------------------------------------------------------------------
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values (l_Name,:l_Line,:l_Type,:l_Command,:l_Rule,:l_Client_ID,:l_Site_ID,l_Name
)
#1(2):20 #2(1):H #3(2):IF #4(27):FA - RETAIN OLD MASTER DATA #5(0): #6(0):
SQL> set serveroutput on
Code & the result:
SQL> DECLARE
2 l_query test.query%TYPE;
3 l_name VARCHAR2 (30) := 'Manu';
4 BEGIN
5 SELECT query INTO l_query FROM test;
6
7 -- bind variables in QUERY
8 FOR cur_l
9 IN (SELECT ROW_NUMBER () OVER (ORDER BY lvl) rn, res l_val
10 FROM ( SELECT LEVEL lvl,
11 REGEXP_SUBSTR (res,
12 '[^,]+',
13 1,
14 LEVEL)
15 res
16 FROM (SELECT SUBSTR (query, INSTR (query, 'values')) res
17 FROM test)
18 CONNECT BY LEVEL <= REGEXP_COUNT (res, ':') + 1)
19 WHERE SUBSTR (res, 1, 1) = ':')
20 LOOP
21 -- values in VALUES
22 FOR cur_v
23 IN (SELECT rn, TRIM (SUBSTR (res, INSTR (res, ':') + 1)) c_val
24 FROM ( SELECT LEVEL rn,
25 REGEXP_SUBSTR (t.c_values,
26 '[^#]+',
27 1,
28 LEVEL)
29 res
30 FROM test t
31 CONNECT BY LEVEL <= REGEXP_COUNT (t.c_values, '#'))
32 WHERE rn = cur_l.rn)
33 LOOP
34 l_query :=
35 REPLACE (l_query,
36 cur_l.l_val,
37 CHR (39) || cur_v.c_val || CHR (39));
38 END LOOP;
39 END LOOP;
40
41 -- Put Manu into l_Name
42 l_query := REPLACE (l_query, 'l_Name', CHR (39) || l_name || CHR (39));
43
44 DBMS_OUTPUT.put_line (l_query);
45 END;
46 /
insert into
tbl_details(CName,Line,Type,Command,Rule,Client_ID,Site_ID,SName)
values ('Manu','20','H','IF','FA - RETAIN OLD MASTER DATA','','','Manu')
PL/SQL procedure successfully completed.
SQL>

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