ORACLE STORED PROCEDURE CURSOR IS WORKING VERY SLOW - oracle

One of my stored procedure recently took around 6 hours which usually takes about 3 hours to complete.
On checking, I found that the cursor is taking the time to execute.
Both the tables are present in my local DB instance.
I need to know what could be the possible reason for this and how the procedure can be fine tuned.
My stored procedure:
create or replace PROCEDURE VMS_DETAILS_D_1 IS
LOG_D1 VARCHAR2(20);
BEGIN
/* IDENTIFY PARTITION */
SELECT partition_name into LOG_D1 FROM all_tab_partitions a WHERE table_name = 'LOG' AND TABLE_OWNER='OWNER1' and partition_position IN
(SELECT MAX (partition_position-1) FROM all_tab_partitions b WHERE table_name = a.table_name AND a.table_owner = b.table_owner);
execute immediate 'DROP TABLE TAB1 PURGE';
COMMIT;
EXECUTE IMMEDIATE 'create table TAB1 Nologging as
select /*+ Parallel(20) */ TRANSACTIONID,TIME_STAMP from OWNER1.log partition('||LOG_D1||')
where ( MESSAGE = ''WalletUpdate| Request for Estel Update is Processed'' or MESSAGE = ''Voucher Core request processed'')';
EXECUTE IMMEDIATE 'CREATE INDEX IDX_TAB1 on TAB1(TRANSACTIONID)';
DBMS_STATS.GATHER_TABLE_STATS (ownname => 'OWNER2' , tabname => 'TAB1',cascade => true, estimate_percent => 10,method_opt=>'for all indexed columns size 1', granularity => 'ALL', degree => 1);
DECLARE
CURSOR resp_cur
IS
select TRANSACTIONID,to_char(max(TIME_STAMP),'DD-MM-YYYY HH24:MI:SS') TIME_STAMP from TAB1
where TRANSACTIONID in (select ORDERREFNUM from TAB2
where ORDERREFNUM like 'BV%') group by TRANSACTIONID;
BEGIN
FOR l IN resp_cur
LOOP
update TAB2
set TCTIME=l.TIME_STAMP
where ORDERREFNUM=l.TRANSACTIONID;
COMMIT;
END LOOP;
END;
end;

First off, DDL has an implicit commit, so you don't need a commit after your drop table.
Secondly, why are you dropping the table and recreating it instead of just truncating the table and inserting into it?
Thirdly, why loop around a cursor to do an update, when you can do it in a single update statement?
If you absolutely must store the data in a separate table, I would rewrite your procedure like so:
CREATE OR REPLACE PROCEDURE vms_details_d_1 IS
log_d1 VARCHAR2(20);
BEGIN
/* IDENTIFY PARTITION */
SELECT partition_name
INTO log_d1
FROM all_tab_partitions a
WHERE table_name = 'LOG'
AND table_owner = 'OWNER1'
AND partition_position IN (SELECT MAX(partition_position - 1)
FROM all_tab_partitions b
WHERE table_name = a.table_name
AND a.table_owner = b.table_owner);
EXECUTE IMMEDIATE 'TRUNCATE TABLE TAB1 reuse storage';
EXECUTE IMMEDIATE 'insert into TAB1 (transactionid, time_stamp)'||CHR(10)||
'select /*+ Parallel(20) */ TRANSACTIONID,TIME_STAMP from OWNER1.log partition(' || log_d1 || ')'||CHR(10)||
'where MESSAGE in (''WalletUpdate| Request for Estel Update is Processed'', ''Voucher Core request processed'')';
EXECUTE IMMEDIATE 'CREATE INDEX IDX_TAB1 on TAB1(TRANSACTIONID)';
dbms_stats.gather_table_stats(ownname => 'OWNER2',
tabname => 'TAB1',
cascade => TRUE,
estimate_percent => 10,
method_opt => 'for all indexed columns size 1',
granularity => 'ALL',
degree => 1);
MERGE INTO tab2 tgt
USING (SELECT transactionid,
max(time_stamp) ts
FROM tab1
GROUP BY transactionid) src
ON (tgt.transactionid = src.transactionid)
WHEN MATCHED THEN
UPDATE SET tgt.tctime = to_char(src.ts, 'dd-mm-yyyy hh24:mi:ss'); -- is tab2.tctime really a string? If it's a date, remove the to_char
COMMIT;
END vms_details_d_1;
/
If you're only copying the data to make it easier to do the update, you don't need to - instead, you can do it all in a single DML statement, like so:
CREATE OR REPLACE PROCEDURE vms_details_d_1 IS
log_d1 VARCHAR2(20);
BEGIN
/* IDENTIFY PARTITION */
SELECT partition_name
INTO log_d1
FROM all_tab_partitions a
WHERE table_name = 'LOG'
AND table_owner = 'OWNER1'
AND partition_position IN (SELECT MAX(partition_position - 1)
FROM all_tab_partitions b
WHERE table_name = a.table_name
AND a.table_owner = b.table_owner);
EXECUTE IMMEDIATE 'MERGE INTO tab2 tgt'||CHR(10)||
' USING (SELECT transactionid,'||CHR(10)||
' MAX(time_stamp) ts'||CHR(10)||
' FROM owner1.log partition(' || log_d1 || ')'||CHR(10)||
' GROUP BY transactionid) src'||CHR(10)||
' ON (tgt.transactionid = src.transactionid)'||CHR(10)||
'WHEN MATCHED THEN'||CHR(10)||
' UPDATE SET tgt.tctime = to_char(src.ts, ''dd-mm-yyyy hh24:mi:ss'')'; -- is tab2.tctime really a string? If it's a date, remove the to_char
COMMIT;
END vms_details_d_1;
/
If you know the predicate(s) which define the partition you're after, you can use those in your query, thus removing the need to find the partition name and therefore needing dynamic SQL.

Okay you procedure needs lots of enhancment:
In the below query you can use user_tab_partitions instead of all_tab_partitions.
SELECT partition_name
into LOG_D1
FROM all_tab_partitions a
WHERE table_name = 'LOG'
AND TABLE_OWNER = 'OWNER1'
and partition_position IN
(SELECT MAX(partition_position - 1)
FROM all_tab_partitions b
WHERE table_name = a.table_name
AND a.table_owner = b.table_owner);
You have to include a checking on the table tab1 incase if it doesnt exists and no need to commit here, its not a DML statement.
execute immediate 'DROP TABLE TAB1 PURGE';
COMMIT;
No need to update the statistics in the procedure, especially its a newly table created and the index is already created, and its only one index.
The above might slightly improve the performance but you have to check that there is an index on table log for column message ( but as i said its wrong modeling) also check query plan on tab2 if it needs a index.

This is the wrong approach, what you're doing is update TAB2 the number of times the records in cursor resp_cur, I would switch to a merge.

Related

Oracle dynamic find number of rows and columns per table

I have the following query, which returns the number of rows per table in a schema.
Can this also be modified to RETURN the number of columns per table too?
CREATE table stats (
table_name VARCHAR2(128),
num_rows NUMBER,
num_cols NUMBER
);
/
DECLARE
val integer;
BEGIN
for i in (SELECT table_name FROM all_tables WHERE owner = 'Schema')
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val);
END LOOP;
END;
/
You can use the ALL_TAB_COLS dictionary table:
DECLARE
val integer;
BEGIN
FOR i IN (
SELECT table_name,
COUNT(*) AS num_cols
FROM all_tab_cols
WHERE owner = 'Schema'
GROUP BY table_name
)
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val, i.num_cols);
END LOOP;
END;
/
db<>fiddle here

Is there a way to make a PLSQL script that lists all columns that IS NULL for every record in a table?

I am working with a huge database with several columns and records. I want to browse a specific table and make a list of the columns that are empty for every record.
Is this possible without refering to all the specific column names?
Thanks for help!
It's possible but if you have a lot data it will last a long time.
create table xxx as select * from dba_objects where rownum < 10000;
prepare test table get table stats. It can be long lasting process.
begin
dbms_stats.gather_table_stats(user,'XXX',estimate_percent =>100);
-- ..
-- others tables to analizye
end;
Generate reports.
select table_name,column_name from user_tab_cols where coalesce(low_value,high_value) is null and table_name in('XXX');
You can use the below script to find out the null columns in your database -
DECLARE
COUNT_COL INT;
SQL_STR VARCHAR2(100);
BEGIN
FOR I IN (SELECT OBJECT_NAME, COLUMN_NAME
FROM USER_OBJECTS UO
JOIN USER_TAB_COLS UTC ON UO.OBJECT_NAME = UTC.TABLE_NAME) LOOP
SQL_STR := 'SELECT COUNT(1) FROM ' || I.OBJECT_NAME || ' WHERE ' || i.COLUMN_NAME || ' IS NOT NULL';
EXECUTE IMMEDIATE SQL_STR INTO COUNT_COL;
IF COUNT_COL = 0 THEN
DBMS_OUTPUT.PUT_LINE(I.COLUMN_NAME);
END IF;
END LOOP;
END;
Here is the fiddle.
Try for all record in table:
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = '<TABLE_NAME>'
AND a.table_name = b.table_name
AND a.num_rows = b.num_nulls
For all table
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = b.table_name
AND a.num_rows = b.num_nulls

Create procedure in oracle DB for finding and deleting foreign key constraints for specific tables

I am new to Oracle, I am trying to write a procedure in Oracle to delete foreign key constraints for a table. I have already done this for MySQL and its working. I am not sure with the syntax, apologies for that, but my query is working If I run it individually. I want to do the same thing(removing foreign key constraints) for multiple tables and don't want to write queries multiple times. hence first I am finding the foreign keys associated with that table , storing them in cursor and later removing all of the foreign keys by creating and executing drop constraints query associated with that table. following code is giving multiple errors to me.
CREATE OR REPLACE PROCEDURE removeConstraintsForTable(vTableName IN varchar2) IS
BEGIN
cName VARCHAR(2048);
sql_stmt VARCHAR2(2048);
CURSOR cur IS
SELECT DISTINCT CONSTRAINT_NAME
FROM ALL_CONSTRAINTS WHERE OWNER= sys_context('userenv','current_schema')
AND TABLE_NAME = vTableName AND CONSTRAINT_TYPE='R';
BEGIN
OPEN cur;
LOOP
FETCH cur INTO cName;
EXIT WHEN cur%notfound;
sql_stmt := CONCAT(CONCAT(CONCAT('ALTER TABLE ',vTableName),CONCAT(' DROP FOREIGN KEY ',cName)),';');
SELECT sql_stmt FROM dual;
INSERT INTO TEMP(Name) VALUES(sql_stmt);
COMMIT;
END LOOP;
END
/
CALL removeConstraintsForTable('table1');
CALL removeConstraintsForTable('table2');
CALL removeConstraintsForTable('table3');
CALL removeConstraintsForTable('table4');
COMMIT;
You have an extra BEGIN right at the start of your procedure, and the final END is missing a semicolon. You shouldn't really be using VARCHAR, and you could declare the cName variable using the data dictionary anyway; however an implicit loop will be simpler, as will using the concatenation operator || instead of nested CONCAT() calls, and the generated statement should not end in a semicolon:
create or replace procedure removeconstraintsfortable(p_table_name in varchar2) is
sql_stmt varchar2(2048);
begin
for rec in (
select owner, constraint_name
from all_constraints
where owner = sys_context('userenv','current_schema')
and table_name = p_table_name
and constraint_type = 'R'
)
loop
sql_stmt := 'ALTER TABLE "' || rec.owner || '"."' || p_table_name || '"'
|| ' DROP CONSTRAINT "' || rec.constraint_name || '"';
insert into temp(name) values(sql_stmt);
end loop;
commit;
end;
/
As pointed out in comments, the generated statement should be drop constraint.
I'm not sure why you're inserting into a table or where you execute the statement, but you can do it all in one if you prefer:
create or replace procedure removeconstraintsfortable(p_table_name in varchar2) is
sql_stmt varchar2(2048);
begin
for rec in (
select owner, constraint_name
from all_constraints
where owner = sys_context('userenv','current_schema')
and table_name = p_table_name
and constraint_type = 'R'
)
loop
sql_stmt := 'ALTER TABLE "' || rec.owner || '"."' || p_table_name || '"'
|| ' DROP CONSTRAINT "' || rec.constraint_name || '"';
dbms_output.put_line(sql_stmt);
execute immediate sql_stmt;
end loop;
end;
/
The dbms_output call just shows you the generated statement(s), before execute immediate executes it, well, immediately.
Quick demo; very basic table set-up:
create table t42 (id number primary key);
create table t43 (id number references t42 (id));
select table_name, constraint_name, constraint_type
from all_constraints
where table_name in ('T42', 'T43');
TABLE_NAME CONSTRAINT_NAME C
------------------------------ ------------------------------ -
T43 SYS_C00138153 R
T42 SYS_C00138152 P
Then call the procedure which shows the generated statement:
set serveroutput on
exec removeConstraintsForTable('T43');
ALTER TABLE "STACKOVERFLOW"."T43" DROP CONSTRAINT "SYS_C00138153"
PL/SQL procedure successfully completed.
and then check the constraint has gone:
select table_name, constraint_name, constraint_type
from all_constraints
where table_name in ('T42', 'T43');
TABLE_NAME CONSTRAINT_NAME C
------------------------------ ------------------------------ -
T42 SYS_C00138152 P
I fixed a number of syntax issues for you. Try this.
CREATE OR REPLACE PROCEDURE removeConstraintsForTable(vTableName IN varchar2) IS
cName VARCHAR2(30); -- identifiers are max 30 chars
sql_stmt VARCHAR2(2048);
CURSOR cur IS
SELECT DISTINCT CONSTRAINT_NAME
FROM USER_CONSTRAINTS
WHERE TABLE_NAME = vTableName AND CONSTRAINT_TYPE='R';
BEGIN
OPEN cur;
LOOP
FETCH cur INTO cName;
EXIT WHEN cur%notfound;
sql_stmt := 'ALTER TABLE ' || vTableName || ' DROP CONSTRAINT ' || cName;
INSERT INTO RANGERADMIN1.TEMP(Name) VALUES(sql_stmt);
END LOOP;
COMMIT;
END removeConstraintsForTable;
/
call removeConstraintsForTable('table1');
call removeConstraintsForTable('table2');
call removeConstraintsForTable('table3');
call removeConstraintsForTable('table4');
-- COMMIT; -- not necessary

Loop tables into pl/sql and display number of rows

I have loop for all the tables into db:
declare
V_TABL_NM ALL_TABLES.TABLE_NAME%TYPE;
BEGIN
FOR GET_TABL_LIST IN (SELECT TABLE_NAME FROM ALL_TABLES )LOOP
V_TABL_NM := GET_TABL_LIST.TABLE_NAME;
DBMS_OUTPUT.PUT_LINE(V_TABL_NM);
END LOOP;
END;
How can I sort my result and add number of records for each tables?
I try below but it does not work:
declare
V_TABL_NM ALL_TABLES.TABLE_NAME%TYPE;
table_row number;
BEGIN
FOR GET_TABL_LIST IN (SELECT TABLE_NAME FROM ALL_TABLES )LOOP
V_TABL_NM := GET_TABL_LIST.TABLE_NAME;
table_row: = select count(*) from TABLE_NAME;
DBMS_OUTPUT.PUT_LINE(V_TABL_NM, table_row);
END LOOP;
END;
You can not make a query that way; TABLE_NAME has no meaning there (and you're missing to use the cursor name), so you need to build a dynamic SQL and run it to put the value into a variable.
Besides, the PUT_LINE does not accept that parameters.
This should work:
DECLARE
table_row NUMBER;
BEGIN
FOR GET_TABL_LIST IN ( SELECT OWNER || '.' || TABLE_NAME AS TABLE_NAME
FROM ALL_TABLES
ORDER BY TABLE_NAME)
LOOP
EXECUTE IMMEDIATE 'select count(*) from ' || GET_TABL_LIST.TABLE_NAME INTO table_row;
DBMS_OUTPUT.PUT_LINE(GET_TABL_LIST.TABLE_NAME || ' - ' || table_row);
END LOOP;
END;
About the ordering, simply add an ORDER BY to the query looping through the tables
This assumes that you have rights to query all the tables listed in ALL_TABLES If you simply need to query all the tables of your schema, use USER_TABLES instead of ALL_TABLES.
to sort the results add order by clausel:
FOR GET_TABL_LIST IN
(
SELECT TABLE_NAME
FROM ALL_TABLES
ORDER BY TABLE_NAME --
)LOOP
to count the records use dynamic sql :
execute immediate 'select count(*) from ' || V_TABL_NM INTO table_row;
This can be done completely without PL/SQL:
select table_name,
to_number(extractvalue(xmltype(dbms_xmlgen.getxml('select count(*) c from '||table_name)),'/ROWSET/ROW/C')) as rowcount
from user_tables
order by rowcount;
also I did:
select TABLE_NAME, NUM_ROWS, LAST_ANALYZED
from user_tables
order by 1;

Dynamically selecting partitions

I have a table with a few hundred partitions and I am generally interested on the latest 35.
Accordingly I am trying to create views which would access these dynamically. i.e. always use the latest in case ones are created.
The query:
select PARTITION_NAME,
PARTITION_POSITION,
NUM_ROWS,
AVG_ROW_LEN
from all_tab_partitions
where
table_name = 'MY_TABLE'
AND PARTITION_NAME <> 'P_LAST'
AND PARTITION_POSITION < (SELECT MAX(PARTITION_POSITION)
FROM all_tab_partitions) - 35
order by 2 DESC
;
Seems to return me the partition names I'm interested, however, I don't manage to use it's results to select the partitions. e.g.:
CREATE OR REPLACE VIEW MY_VIIEW AS
WITH t AS ( [Above query] )
SELECT * FROM
MY_TABLE PARTITION (SELECT /*+ FIRST_ROWS(1) */ PARTITION_NAME
from t);
(not the actual view, just an example)
So how do I do that? How do I create a view which will acess always the latest partition (execpt of "MAX")?
I am using Oracle 10g
thanks
You can do it using PL/SQL only
create or replace package my_table_ is
type t_records is table of my_table%rowtype;
function getpart(c_parts sys_refcursor) return t_records pipelined;
end;
create or replace package body my_table_ is
function getpart(c_parts sys_refcursor) return t_records pipelined is
v_partition all_tab_partitions.partition_name%type;
v_row my_table%rowtype;
c_mytab sys_refcursor;
begin
loop
fetch c_parts into v_partition;
exit when c_parts%notfound;
open c_mytab for 'select * from my_table partition ('||v_partition||')';
loop
fetch c_mytab into v_row;
exit when c_mytab%notfound;
pipe row (v_row);
end loop;
end loop;
end;
end;
Now you can
select * from table(my_table_.getpart(cursor(<QUERY_RETURNING_PARTITION_NAMES>)));
May be you can construct view's query using batch of union all statements with partition name in each statement, e.g.
create view p as
select * from my_table partition (part1)
union all
select * from my_table partition (part1)
...
union all
select * from my_table partition (part35)
Ok... I don't think your can use the Partition-Names, but you can use the Starting-Values of the Partitions to select the Data matching these Partitions...
So you View would look like this:
SELECT * FROM my_table WHERE date_col > get_part_limit( 'my_table', 35 ):
Where date_col is the column you use for partitioning - and get_part_limit is a stored function you write like this:
...
BEGIN
SELECT high_value FROM all_tab_partitions
INTO local_var
WHERE table_name = parameter_name
AND PARTITION_POSITION = MAX... - 35
EXECUTE IMMEDIATE 'SELECT '||local_var||' FROM DUAL' INTO local_return_value;
RETURN local_return_value;
END;
partitions are designed to be transparent for the data, so when you write a query, you simply don't know how your data is stored.
I see only one possibility to hit a particular partition: your WHERE clause should match values to the partitioned columns of latest (or latest 5) partition.
Next question is to build this WHERE clause on the fly. You already know that there is plenty of information in oracle dictionary. So you will read that and create a constructor to convert metadata conditions back into SQL.
irl we do exactly the same thing and use falco's solution like.
Here is my code:
create or replace function longToDate( myOwner varchar2,
mytable_name in varchar2,
mypartition_name in varchar2
) return date
as
cDate date;
cvar varchar2(1024);
rq varchar2(1024);
infiniteValue EXCEPTION;
PRAGMA EXCEPTION_INIT(infiniteValue, -00904);
begin
select high_value into cvar FROM dba_tab_partitions t where t.table_owner=myOwner and table_name=mytable_name and partition_name=mypartition_name;
rq:='select '||cvar||' from dual';
execute immediate rq into cDate;
return cdate;
EXCEPTION
WHEN infiniteValue
then return'01 jan 3000';
when others
then return null;
end longToDate;
Ant the view is something like this
create or replace view last_35 as
with maxdate as
(select longToDate(p.table_owner,p.table_name,p.partition_name) mydate,
rank()over(order by p.partition_position desc) mypos,
p.* from all_tab_partitions p
where p.table_name='MY_TABLE'
)
select /*+full(a)*/* from MY_TABLE a, maxdate
where MY_TABLE.partition_name>maxdate.mydate
and maxdate.mypos=35

Resources