PROCEDURE purge_partitions
(
p_owner IN VARCHAR2
,p_name IN VARCHAR2
,p_retention_period IN NUMBER
) IS
BEGIN
FOR partition_rec IN (SELECT partition_name
,high_value
FROM dba_tab_partitions
WHERE table_owner = p_owner
AND table_name = p_name)
LOOP
IF SYSDATE >= add_months(to_date(substr(partition_rec.high_value
,12
,19)
,'YYYY-MM-DD HH24:MI:SS')
,p_retention_period)
THEN
execute_immediate('ALTER TABLE ' || p_owner || '.' ||
p_name || ' DROP PARTITION ' ||
partition_rec.partition_name)
END IF;
END LOOP;
END purge_partitions;
Purge_Partitions procedure deals with dropping partitions based on
specific retention priods mentioned in a seperate Config Table. I am
now trying to enhance this functionality which will take care of
rebuilding global indexes of those partitioned tables. Not sure how to go about this, any help is highly appreciated.
Consider the update_index_clauses, which keeps the indexes valid.
See the documentation and consideration for global indexes here
in your case it will be:
alter table ttttt drop partition pppppp update global indexes;
Alternatively let the indices be invalidated in the DROP PARTITION and rebuild them with alter index xxx rebuild. You can get the list of indexes to rebuild from this query
select OWNER, INDEX_NAME
from all_indexes
where owner = 'ooo' and table_name = 'tttttt' and status = 'UNUSABLE';
Related
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.
I am trying to write a Procedure which essentially drops partitions from several tables which are stored in multiple schemas. End goal is to then create a dbms scheduler which will run this procedure every day and check for partitions that hold data older than 6 months. How to add functionality of looking for partitions across multiple schemas ?
I have created a Procedure which drops a partition only from a specific table.
PROCEDURE purge_ops_log_range_parts IS
BEGIN
FOR partition_rec IN (SELECT partition_name
,high_value
FROM user_tab_partitions
WHERE table_name =
'OPSWIRE_LOG_RANGE_PARTS')
LOOP
IF SYSDATE >= add_months(to_date(substr(partition_rec.high_value
,12
,19)
,'YYYY-MM-DD HH24:MI:SS')
,6)
THEN
execute_immediate('ALTER TABLE OPS_LOG_RANGE_PARTS DROP PARTITION ' ||
partition_rec.partition_name);
END IF;
END LOOP;
END purge_ops_log_range_parts;
Output is deleting partition from a specific table only however it does not look for multiple tables in various schemas.
Use the DBA_TAB_PARTITIONS or ALL_TAB_PARTITIONS views instead of USER_TAB_PARTITIONS. The former two views contain a TABLE_OWNER (i.e. schema) column which should help you accomplish your goal.
You can then parameterize your procedure to take the owner and table names as parameters:
PROCEDURE purge_ops_log_range_parts(pinOwner IN VARCHAR2,
pinTable_name IN VARCHAR2)
IS
BEGIN
FOR partition_rec IN (SELECT partition_name
,high_value
FROM DBA_TAB_PARTITIONS
WHERE TABLE_OWNER = pinOwner AND
table_name = pinTable_name)
LOOP
IF SYSDATE >= add_months(to_date(substr(partition_rec.high_value
,12
,19)
,'YYYY-MM-DD HH24:MI:SS')
,6)
THEN
execute_immediate('ALTER TABLE ' || pinOwner || '.' ||
pinTable_name || ' DROP PARTITION ' ||
partition_rec.partition_name);
END IF;
END LOOP;
END purge_ops_log_range_parts;
Best of luck.
I have this table as my main table (100M rows):
create table prova_log(
id_dispositive number,
type number,
date_verification date,
status number
)
partition by range (date_verification) interval (NUMTODSINTERVAL(3,'DAY'))
subpartition by list (type)
subpartition TEMPLATE (
SUBPARTITION type1 VALUES (1),
SUBPARTITION type2 VALUES (2),
SUBPARTITION type3 VALUES (3),
SUBPARTITION type4 VALUES (4)
)
(
partition p0816 values less than (to_date('01/09/2016','dd/mm/yyyy'))
);
And I want to make some sort of backup with older values, so I created this (0 rows):
create table prova_log_old (
id_dispositive number,
type number,
date_verification date,
status number
)
partition by range (date_verification) interval (NUMTODSINTERVAL(3,'DAY'))
subpartition by list (type)
subpartition TEMPLATE (
SUBPARTITION type1 VALUES (1),
SUBPARTITION type2 VALUES (2),
SUBPARTITION type3 VALUES (3),
SUBPARTITION type4 VALUES (4)
)
(
partition p_old values less than (to_date('01/09/2016','dd/mm/yyyy'))
);
So I want to move/copy/exchange (whatever term) old partitions (15 days+) to prova_log_old.
To do so I've created this job:
PROCEDURE move_data_from_huge_table
IS
-- This will move all data after 'vcountdaystokeepdata' days
vcountdaystokeepdata NUMBER := 15;
vcountdatainsidepartition NUMBER := 0;
BEGIN
FOR item IN
(SELECT *
FROM (SELECT partition_name,
TO_DATE
(TRIM
('''' FROM REGEXP_SUBSTR
(EXTRACTVALUE
(DBMS_XMLGEN.getxmltype
( 'select high_value from all_tab_partitions where table_name='''
|| table_name
|| ''' and table_owner = '''
|| table_owner
|| ''' and partition_name = '''
|| partition_name
|| ''''
),
'//text()'
),
'''.*?'''
)
),
'syyyy-mm-dd hh24:mi:ss'
) high_value
FROM all_tab_partitions
WHERE table_name = 'PROVA_LOG')
WHERE high_value < SYSDATE - vcountdaystokeepdata)
LOOP
EXECUTE IMMEDIATE 'alter table PROVA_LOG EXCHANGE PARTITION '
|| item.partition_name
|| ' with table PROVA_LOG_OLD';
EXECUTE IMMEDIATE 'select count(*) from PROVA_LOG partition ('
|| item.partition_name
|| ')'
INTO vcountdatainsidepartition;
IF vcountdatainsidepartition = 0
THEN
EXECUTE IMMEDIATE 'ALTER TABLE PROVA_LOG DROP PARTITION '
|| item.partition_name
|| '';
END IF;
END LOOP;
END;
But when I run the procedure I got
ORA-14292 partitioning type of table must match subpartitioning type of composite partition
I assume that I must have a partition in my backup table with the same name as my main partitioned table right?
How can I make this work?
I tried to add a partition to my backup table but without success..It's important to mention that all partition's name are random (oracle generates it).
I still don't understand why you want to move the partitions, anyway I have a solution.
First of all, you can address a partition either like
SELECT COUNT(*) FROM PROVA_LOG PARTITION (SYS_P7138);
or you can do it as
SELECT COUNT(*) FROM PROVA_LOG PARTITION FOR (TO_DATE('2016-10-01', 'YYYY-MM-DD'));
or if you prefer DATE literals
SELECT COUNT(*) FROM PROVA_LOG PARTITION FOR (DATE '2016-10-01');
An automatic solution for you problem could be this one:
DECLARE
CURSOR TabPartitions IS
SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'PROVA_LOG'
ORDER BY 1,2;
ts DATE;
BEGIN
FOR aPart IN TabPartitions LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
IF ts <> DATE '2016-09-10' AND ts < SYSDATE - 15 THEN
--EXECUTE IMMEDIATE 'INSERT INTO PROVA_LOG_OLD SELECT * FROM PROVA_LOG PARTITION FOR (DATE '''||TO_CHAR(ts, 'yyyy-mm-dd')||''')';
--EXECUTE IMMEDIATE 'ALTER TABLE PROVA_LOG DROP PARTITION FOR (DATE '''||TO_CHAR(ts, 'yyyy-mm-dd')||''') UPDATE GLOBAL INDEXES';
EXECUTE IMMEDIATE 'ALTER TABLE PROVA_LOG EXCHANGE PARTITION FOR (DATE '''||TO_CHAR(ts, 'yyyy-mm-dd')||''') WITH TABLE PROVA_LOG_OLD INCLUDING INDEXES';
END IF;
END LOOP;
END;
Your backup table must be like this:
CREATE TABLE prova_log_old (
id_dispositive NUMBER,
TYPE NUMBER,
date_verification DATE,
status NUMBER
)
PARTITION BY LIST (TYPE)
(
PARTITION type1 VALUES (1),
PARTITION type2 VALUES (2),
PARTITION type3 VALUES (3),
PARTITION type4 VALUES (4)
);
or no partitioning at all
CREATE TABLE prova_log_old (
id_dispositive NUMBER,
TYPE NUMBER,
date_verification DATE,
status NUMBER
);
You are doing it wrong. You exchange partition with all partitioned table not with it partition, just look one more at your code
EXECUTE IMMEDIATE 'alter table PROVA_LOG EXCHANGE PARTITION '
|| item.partition_name
|| ' with table PROVA_LOG_OLD';
In case of exchange partition you should do as follows
Create empty table without partition with same structure like PROVA_LOG but not partitioned.
Exchange partition in production table with new_table
Exchange partition in hist table with new_table
A table TMP has 5 partitions, namely P_1, P_2,....P_5.
I need to drop some partitions of TMP; the partitions to drop are derived by another query.
Ex:
ALTER TABLE TMP DROP PARTITIONS (SELECT ... From ... //expression to get partition names )
Let's say the SELECT statement returns P_1 & P_5. The part query of the ALTER statement above doesn't work. Is there any way to drop partitions with input from a SELECT statement?
You can use dynamic sql in anonymous pl/sql block;
Begin
for i in (select part_name from ... //expression to get partition names) loop
execute immediate 'ALTER TABLE TMP DROP PARTITION ' || i.part_name;
end loop;
end;
For dropping multiple partitions on a go then;
declare
v_part varchar(1000);
Begin
select LISTAGG(partition_name, ', ') WITHIN GROUP (ORDER BY partition_name DESC)
into v_part
from ... //expression to get partition names;
execute immediate 'ALTER TABLE TMP DROP PARTITION ' || v_part;
end;
You may use the following sql to generate DML for dropping multiple table partitions.
select 'ALTER TABLE ' || TABLE_OWNER || '.' || TABLE_NAME || ' DROP PARTITION ' || '"' || PARTITION_NAME || '";' from DBA_TAB_PARTITIONS
where TABLE_NAME='%YOUR_PATTERN%'order by PARTITION_NAME;
You'll need to use dynamic SQL. Something this:
begin
for prec in (SELECT ... From ... //expression to get partition names )
loop
execute immediate 'ALTER TABLE TMP DROP PARTITION '
|| prec.partition_name;
end loop;
end;
/
Clearly you need to have complete faith that your query will return only the partitions you want to drop. Or equivalent faith in your Backup & Recover plans :)
Alternatively you can use a similar approach to generate a drop script which you can review before you run it.
You have to use pl/sql block for dropping partitions from a table with select query. Use listagg for making a comma separated list.
DECLARE
var1 varchar2(50);
BEGIN
SELECT listagg(Partition_names) into var1 from table_name//expression to get partition names ;
execute immediate 'alter table tmp drop PARTITION'||var1 ;
END;
Example on listagg
select LISTAGG(partition_name,',') within group(order by table_name) as comma_list
from ALL_TAB_PARTITIONS where TABLE_owner='OWNER' AND TABLE_NAME='TABLE_NAME'
Maybe it's could somebody help.
This script drop all partitions for all partition tables for specific schema. I use it with clear DB with METADATA, for changing started (referencial) partition.
ALTER TABLE SCHEMA_1.TABLE_1
SET INTERVAL ();
ALTER TABLE SCHEMA_1.TABLE_2
SET INTERVAL ();
ALTER TABLE SCHEMA_1.TABLE_3
SET INTERVAL ();
set lines 100
set heading off
spool runme.sql
select 'ALTER TABLE ' || TABLE_OWNER || '.' || TABLE_NAME || ' DROP PARTITION ' || '"' || PARTITION_NAME || '";' from DBA_TAB_PARTITIONS
where
TABLE_OWNER='SCHEMA_1'
-- and TABLE_NAME='TABLE_%'
and PARTITION_NAME LIKE 'SYS_P%'
;
#runme
ALTER TABLE SCHEMA_1.TABLE_1
SET INTERVAL (NUMTOYMINTERVAL(1,'MONTH'));
ALTER TABLE SCHEMA_1.TABLE_1
SET INTERVAL (NUMTOYMINTERVAL(1,'MONTH'));
ALTER TABLE SCHEMA_1.TABLE_3
SET INTERVAL (NUMTOYMINTERVAL(1,'MONTH'));
Yes, the script is semi-manual, but I think it's more safety.
ALTER TABLE ... INTERVAL it's need for droping last partition.
Interval must be same that it was before
Following statement allows me to retrieve information about a table's partitions:
select table_name, partition_name, high_value from user_tab_partitions where table_name = 'T1';
problem is that, for some unknown reason, high_value column's values are expressed as:
TO_DATE(' 2015-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')
is it possible to retrieve them as date? Or at least do something like "eval" on that expression?
That field is defined as a long type, so depending on the partitioning scheme, it can hold various values (a string like 'ABC', a number 123, a string representation of a date, whatever).
Anyway, sounds like you want to get data from a particular partition, and you're trying to figure out the partition name from the metadata. If thats the case, you can simply use the partition for clause:
select * from my_partitioned_table partition for (to_date('20150801', 'YYYYMMDD'));
Which would select data from the partition housing that particular date (assuming your partitioning by date). This is particularly useful in interval partitioning, where Oracle assigns partition names like SYS_xxxx which seem arbitrary at best.
If you'd like to drop partitions older than a given date, its a bit more tricky. The above syntax is for selecting data, not DDL (alter table). To do that, you could do something like this (loosely tested):
Create a function to identify which partitions hold data with dates less than a given reference date:
create or replace function fn_partition_is_earlier(i_part_tab_name varchar2, i_partition_position number, i_ref_date in date)
return number
is
l_date_str varchar2(2000);
l_date date;
begin
execute immediate 'select high_value from all_tab_partitions where table_name = :tab and partition_position = :pos'
into l_date_str
using i_part_tab_name, i_partition_position;
execute immediate 'select ' || l_date_str || ' from dual' into l_date;
if (l_date < i_ref_date) then
return 1;
end if;
return 0;
end;
Use the function as follows:
with part_name as (
select partition_name
from (
select fn_partition_is_earlier(p.table_name, p.partition_position, to_date('20130501', 'YYYYMMDD')) should_drop_flag, p.*
from all_tab_partitions p
where table_name = 'MY_TAB'
)
where should_drop_flag = 1
)
select 'alter table MY_TAB drop partition ' || part_name.partition_name || ' update global indexes;'
from part_name;
The output would give you the script you'd have the DBAs run off hours.
Hope that helps.