Execute multiple Oracle SQL statements simultaneously - oracle

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>

Related

Update second column

I have a table "TEST_TABLE" with two columns TABLE_NAME and RECORD_COUNT.
enter image description here
We want to update the column RECORD_COUNT by taking the total records in table specified in TABLE_NAME.
You could do this with dynamic SQL but why be complicated?
Create a view
Create view my_tables as
Select
'table_1' as "table name",
count(*) as "rows"
From table_1
Add the following for each table
Union all
Select 'table_2',count(*) From table_2
You can then use the view like a table:
Select * from my_tables;
OK, but - why wouldn't you use information Oracle already provides for you? You should regularly gather statistics anyway, so:
SQL> execute dbms_stats.gather_schema_stats(ownname => 'SCOTT', estimate_percent => null);
PL/SQL procedure successfully completed.
and then fetch num_rows from user_tables:
SQL> select table_name, num_rows from user_tables where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
It can't be much worse than your attempt (due to possible frequent changes to tables' contents; inserts and deletes) because you'd collect your own data periodically (how often?) anyway.
If it has to be your way, then you'd use dynamic SQL, looping through all tables in your schema and actually count number of rows:
SQL> create table test_table
2 (table_name varchar2(30),
3 num_rows number);
Table created.
SQL> create or replace procedure p_test as
2 l_cnt number;
3 begin
4 execute immediate 'truncate table test_table';
5 for cur_R in (select table_name from user_tables) loop
6 execute immediate 'select count(*) from ' ||cur_R.table_name into l_Cnt;
7 insert into test_table (table_name, num_rows) values (cur_r.table_name, l_cnt);
8 end loop;
9 end;
10 /
Procedure created.
Running the procedure and viewing the result:
SQL> exec p_test;
PL/SQL procedure successfully completed.
SQL> select * From test_Table where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
Note that performance will suffer as number of tables and number of rows stored in them grows.
If I were you, I'd go for the first option.

How to run a query on different schemes at once

I want to run a query on different schemes to get data and export it. I use the following code
DECLARE
sql_statment VARCHAR2(2000);
BEGIN
FOR c IN (SELECT brchcode FROM brchs) LOOP
sql_statment := 'select distinct ''' || c.brchcode ||''', t.risuid from ' || c.brchcode ||
'.reg_individualacnt_detail t
where t.historytypecode = 60';
EXECUTE IMMEDIATE sql_statment;
END LOOP;
END;
where brchcode is the name of different schemes
I can't see any output. what can I do?
Code you wrote can't work as you have to return the result into something; it is PL/SQL and requires an INTO clause. As you chose to return two values (columns) and multiple rows, that can't be a scalar variable; you could pick ref cursor or a collection, for example.
Here's an example which shows one option.
I'll be using two schemas: SCOTT (current schema) and HR. Both will be having the DEPT table.
As Scott already has it, I'll create one in HR schema and grant access to Scott (otherwise, Scott won't even see it and the procedure (i.e. the function) will fail):
SQL> connect hr/hr
Connected.
SQL> create table dept (deptno number, dname varchar2(10), loc varchar2(10));
Table created.
SQL> insert into dept values (55, 'IT', 'Zagreb');
1 row created.
SQL> grant select on dept to scott;
Grant succeeded.
SQL> commit;
Commit complete.
Back to Scott, to create a table (which contains schema names I'll be selecting from) and a function. I chose to return REF CURSOR; you could return something else, if you want.
SQL> connect scott/tiger
Connected.
SQL> create table brchs (brchcode varchar2(10));
Table created.
SQL> insert into brchs (brchcode)
2 select 'scott' from dual union all
3 select 'hr' from dual;
2 rows created.
SQL> create or replace function f_br
2 return sys_refcursor
3 is
4 l_str varchar2(4000);
5 l_rc sys_refcursor;
6 begin
7 for cur_r in (select brchcode from brchs) loop
8 l_str := l_str ||
9 'union all
10 select ' || chr(39)|| cur_r.brchcode ||chr(39) || ', d.dname
11 from ' || cur_r.brchcode ||'.dept d
12 where d.deptno > 0';
13 end loop;
14
15 l_str := ltrim(l_str, 'union all');
16
17 open l_rc for l_str;
18 return l_rc;
19 end;
20 /
Function created.
SQL>
Finally, testing:
SQL> select f_br from dual;
F_BR
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'SCOT DNAME
----- --------------
scott ACCOUNTING
scott RESEARCH
scott SALES
scott OPERATIONS
hr IT
SQL>

What is the Oracle equivalent of TG_OP and TG_NAME in Postgres?

I want to create a trigger that just inserts the name of the modified table and the operation performed on it. Kind of like:
BEGIN
INSERT INTO audit_table VALUES(user,tg_name,tg_op);
END;
However I can't find what's the equivalent in Oracle to tg_name and tg_op from Postgres.
Huh, there's nothing that simple in Oracle, as far as I can tell (but I admit - I may be very wrong and someone might know better). Anyway, until that better answer comes, here's a little bit of coding; see if it helps.
I'm creating a test table and a log table (though, I'm not sure how good your log table actually is; you don't know when something was done etc. so - I took some freedom to add at least the DATE column).
SQL> create table test as select * From dept;
Table created.
SQL> create table test_log
2 (username varchar2(30),
3 c_date date,
4 table_name varchar2(30),
5 action varchar2(1));
Table created.
A trigger: unfortunately, I don't know whether there's a built-in function which returns name of an object (a table, in our case) you're doing something with, so I'm querying USER_OBJECTS.
SQL> create or replace trigger trg_test_log
2 after insert or update or delete on test
3 for each row
4 declare
5 l_table_name varchar2(30);
6 l_action varchar2(1);
7 begin
8 select object_name
9 into l_table_name
10 from user_objects
11 where object_id = dbms_rowid.rowid_object(nvl(:new.rowid, :old.rowid));
12
13 if inserting then
14 l_action := 'I';
15 elsif updating then
16 l_action := 'U';
17 elsif deleting then
18 l_action := 'D';
19 end if;
20
21 insert into test_log (username, c_date, table_name, action)
22 values (user, sysdate, l_table_name, l_action);
23 end;
24 /
Trigger created.
And ... action!
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select * From test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> delete from test where deptno = 40;
1 row deleted.
SQL> update test set loc = 'CROATIA' where deptno = 10;
1 row updated.
SQL> insert into test (deptno, dname, loc) values (99, 'STACK', 'OVERFLOW');
1 row created.
SQL> select * From test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING CROATIA
20 RESEARCH DALLAS
30 SALES CHICAGO
99 STACK OVERFLOW
SQL> select * From test_log;
USERNAME C_DATE TABLE_NAME A
------------------------------ ------------------- ------------------------------ -
SCOTT 23.09.2018 08:44:17 TEST D
SCOTT 23.09.2018 08:44:17 TEST U
SCOTT 23.09.2018 08:44:17 TEST I
SQL>
There is no point in having special attribute for table_name and operation in DML trigger.
DML trigger can be created only on ONE table so you can hardcode its name in trigger itself.
Conditional predicates INSERTING, DELETING, and UPDATING can be used to derive operation name.
If, howevere, you want to create schema level trigger then you can use predefined attributes ora_*.
create table audit_table
(
user_name varchar2(30),
table_name varchar2(30),
action varchar2(30)
);
create or replace trigger schema_trigger
before create or alter
on schema
begin
insert into audit_table
(user_name, table_name, action)
values
(ora_login_user, ora_dict_obj_name, ora_sysevent);
end schema_trigger;
/
PL/SQL Triggers

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.

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