ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDOTBS' - oracle

I am running cron job which have following PL/SQL block:
declare
begin
--insert into DB_LOGS_TEMP table from DB_LOGS table
INSERT INTO DB_LOGS_TEMP SELECT * FROM DB_LOGS WHERE DB_LOG_ID NOT IN(SELECT DB_LOG_ID from DB_LOGS_TEMP );
--keep the lat 10 records and delete other records
DELETE DB_LOGS where rowid in (
select rid from (
select t.rowid rid,
row_number() over(partition by T.DB_LOG_ID order by T.TIMESTAMP desc) as rn
from DB_LOGS t)
where rn > 10);
end;
The DB_LOGS table has 10247302 rows. When the cron job run it throws an error as ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDOTBS'. Does increasing the tablespce is the only solution for this issue and how to do that? The UNDOTBS has 524288000 bytes.

It works for me while increasing the tablespace and making autoextend on.
ALTER DATABASE DATAFILE '/vg01lv11/oracle//data/undotbs_d1_O2P00R11.dbf' AUTOEXTEND ON MAXSIZE 10g;
ALTER DATABASE DATAFILE '/vg01lv11/oracle//data/undotbs_d1_O2P00R11.dbf'
RESIZE 1000M;

If you can afford deleting in different transactions:
DECLARE
i PLS_INTEGER;
BEGIN
--insert into DB_LOGS_TEMP table from DB_LOGS table
INSERT INTO DB_LOGS_TEMP
SELECT *
FROM DB_LOGS
WHERE DB_LOG_ID NOT IN
(SELECT DB_LOG_ID FROM DB_LOGS_TEMP
);
COMMIT;
i:=50;
--keep the lat 10 records and delete other records
WHILE i>=10
LOOP
DELETE DB_LOGS
WHERE rowid IN
(SELECT rid
FROM
(SELECT t.rowid rid,
row_number() over(partition BY T.DB_LOG_ID order by T.TIMESTAMP DESC) AS rn
FROM DB_LOGS t
)
WHERE rn > i
);
COMMIT;
i:=i-5;
END LOOP;
END;

Related

Add a new partition to other partition based on the date column and 1 partition for each day

I want to Design a table like table F and partition it in such a way that a new partition is created by adding a new data assuming it is not in the existing partitions. (partition based on the date column and 1 partition for each day) . I am new in oracle, please help me ,what is the best idea ? How can I write this code .
Table F is :
DATE Amount ID
2015-05-18 1000 1
2015-05-19 2000 2
2015-05-20 3000 3
2015-05-21 4000 4
2015-05-21 5000 5
2015-05-21 3000 6
2015-05-22 2002 7
You can create several different partitions on a date column ie ( daily, weekly, monthly, quarterly or yearly) and include a local or global index on the column you need to decide what fits your needs
Below is an example of a weekly PARTITION with a global index. The PARTITIONs will be automatically added with a system generated name
when a new date is inserted into the table.
/* weekly PARTITION */
CREATE TABLE t1 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (7, 'DAY') ) (
PARTITION OLD_DATA VALUES LESS THAN (DATE '2022-04-01')
);
/
INSERT into t1 (dt)
with dt (dt, interv) as (
select date '2022-04-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-08-31')
select dt from dt;
/
create index t1_global_ix on T1 (dt);
/
Every time a new partition is created Oracle generates a system name, which is quite cryptic
Here is a list of the PARTITION names, which Oracle generated when I loaded the data above
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
PARTITION_NAME
OLD_DATA
SYS_P458773
SYS_P458774
SYS_P458775
SYS_P458776
SYS_P458777
SYS_P458778
SYS_P458779
SYS_P458780
SYS_P458781
SYS_P458782
SYS_P458783
SYS_P458784
SYS_P458785
SYS_P458786
SYS_P458787
SYS_P458788
SYS_P458789
SYS_P458790
SYS_P458791
SYS_P458792
SYS_P458793
SYS_P458794
Although PARTITION management will work with system GENERATED PARTITION names, I use the procedure below to rename them to something more meaningful.
Let's create and run the procedure and take a look at the names again. As you can see, since we are working with weekly partitions the name is P_ for partittion YYYY 4 digit year the PARTITION is in, W for Week of the year, and ## for the week number within the year.
I would suggest using the scheduler to run this process at least once a day. You can run it as many times as you want as it will not cause any harm.
CREATE OR REPLACE PROCEDURE MaintainPartitions IS EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
CURSOR PartTables IS
SELECT TABLE_NAME, INTERVAL
FROM USER_PART_TABLES
WHERE PARTITIONING_TYPE = 'RANGE'
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$%'
and interval is not null
ORDER BY PARTITION_POSITION;
ym INTERVAL YEAR TO MONTH;
ds INTERVAL DAY TO SECOND;
newPartName VARCHAR2(30);
PERIOD TIMESTAMP;
BEGIN
FOR aTab IN PartTables LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ds;
ym := NULL;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ym;
ds := NULL;
END;
FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT PERIOD;
IF ds IS NOT NULL THEN
IF ds >= INTERVAL '7' DAY THEN
-- Weekly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"IYYY"W"IW';
ELSE
-- Daily partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMMDD';
END IF;
ELSE
IF ym = INTERVAL '3' MONTH THEN
-- Quarterly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYY"Q"Q';
ELSE
-- Monthly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMM';
END IF;
END IF;
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 MaintainPartitions;
/
EXEC MaintainPartitions
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
PARTITION_NAME
OLD_DATA
P_2022W14
P_2022W15
P_2022W16
P_2022W17
P_2022W18
P_2022W19
P_2022W20
P_2022W21
P_2022W22
P_2022W23
P_2022W24
P_2022W25
P_2022W26
P_2022W27
P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34
SELECT COUNT(*) FROM USER_TAB_PARTITIONS
COUNT(*)
31
Next step is setting up your RETENTION table. There should be an entry for each interval range PARTITION.
The RETENTION value is for you to decide. In my example, I chose 30 days fir table T1. This means, when the high value for a PARTITION is greater than 30 days its eligible to be dropped. So chose wisely when setting up these values.
Note: I listed the names of other tables to show how each table has its own value.
CREATE TABLE PARTITION_RETENTION (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
TABLE_NAME VARCHAR2(30),
RETENTION INTERVAL DAY(3) TO SECOND(0),
CONSTRAINT
partition_retention_pk primary key (table_name),
CONSTRAINT CHK_NON_ZERO_DAYS CHECK (
RETENTION > INTERVAL '0' DAY
),
CONSTRAINT CHK_WHOLE_DAYS CHECK (
EXTRACT(HOUR FROM RETENTION) = 0
AND EXTRACT(MINUTE FROM RETENTION) = 0
AND EXTRACT(SECOND FROM RETENTION) = 0
)
);
insert into PARTITION_RETENTION (TABLE_NAME, RETENTION)
select 'T0', interval '10' day from dual union all
select 'T1', interval '30' day from dual union all
select 'T2', interval '15' day from dual union all
select 'T3', interval '30' day from dual union all
select 'T4', 15 * interval '1' day from dual union all
select 'T5', 5 * interval '1 00:00:00' day to second from dual;
Below are 3 procedures that need to be created.
The ddl procedure is a wrapper, which shows you what is being processed and how long it takes.
The rebuild_index procedure is obvious it rebuilds any invalid indexes. As I mentioned above if you are using a global index and a PARTITION is dropped then the index needs to be rebuilt. I hardcoded parallel 4 in this example but if you have plenty if CPU power you may want to increase the number to fit your needs.
In addition, there are other ways indexes can be marked unusable so you may want to consider scheduling that task.
Lastly, is the anonymous block. Which actually drops the PARTITIONS for, which the retention PERIOD has passed. This needs to be scheduled once a day!!
If you look carefully at the anonymous block the last step is a call to the rebuild index procedure. So if an index is unusable it will be rebuilt.
Now let's run the process and see what happens.
CREATE OR REPLACE PROCEDURE ddl(p_cmd varchar2)
authid current_user
is
t1 pls_integer;
BEGIN
t1 := dbms_utility.get_time;
dbms_output.put_line(p_cmd);
execute immediate p_cmd;
dbms_output.put_line((dbms_utility.get_time - t1)/100 || ' seconds');
END;
/
CREATE OR REPLACE PROCEDURE rebuild_index
authid current_user
is
BEGIN
for i in (
select index_owner, index_name, partition_name, 'partition' ddl_type
from all_ind_partitions
where status = 'UNUSABLE'
union all
select owner, index_name, null, null
from all_indexes
where status = 'UNUSABLE'
)
loop
if i.ddl_type is null then
ddl('alter index '||i.index_owner||'.'||i.index_name||' rebuild parallel 4 online');
else
ddl('alter index '||i.index_owner||'.'||i.index_name||' modify '||i.ddl_type||' '||i.partition_name||' rebuild parallel 4 online');
end if;
end loop;
END;
/
DECLARE
CANNOT_DROP_LAST_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_LAST_PARTITION, -14758);
CANNOT_DROP_ONLY_ONE_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_ONLY_ONE_PARTITION, -14083);
ts TIMESTAMP;
CURSOR TablePartitions IS
SELECT TABLE_NAME, PARTITION_NAME, p.HIGH_VALUE, t.INTERVAL, RETENTION, DATA_TYPE
FROM USER_PART_TABLES t
JOIN USER_TAB_PARTITIONS p USING (TABLE_NAME)
JOIN USER_PART_KEY_COLUMNS pk ON pk.NAME = TABLE_NAME
JOIN USER_TAB_COLS tc USING (TABLE_NAME, COLUMN_NAME)
JOIN PARTITION_RETENTION r USING (TABLE_NAME)
WHERE pk.object_type = 'TABLE' AND
t.partitioning_type = 'RANGE' AND
REGEXP_LIKE (tc.data_type, '^DATE$|^TIMESTAMP.*');
BEGIN
FOR aPart IN TablePartitions LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
IF ts < SYSTIMESTAMP - aPart.RETENTION THEN
BEGIN
ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);
WHEN CANNOT_DROP_LAST_PARTITION THEN
BEGIN
DBMS_OUTPUT.PUT_LINE('Drop last partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL ()';
ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
-- Depending on the order the "last" partition can be also the "only" partition at the same time
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);
END;
END;
END IF;
END LOOP;
rebuild_index();
END;
alter table T1 drop partition OLD_DATA
.02 seconds
alter table T1 drop partition P_2022W14
.01 seconds
alter table T1 drop partition P_2022W15
.02 seconds
alter table T1 drop partition P_2022W16
.01 seconds
alter table T1 drop partition P_2022W17
.02 seconds
alter table T1 drop partition P_2022W18
.01 seconds
alter table T1 drop partition P_2022W19
.02 seconds
alter table T1 drop partition P_2022W20
.01 seconds
alter table T1 drop partition P_2022W21
.01 seconds
alter table T1 drop partition P_2022W22
.02 seconds
alter table T1 drop partition P_2022W23
.01 seconds
alter table T1 drop partition P_2022W24
.01 seconds
alter table T1 drop partition P_2022W25
.01 seconds
alter table T1 drop partition P_2022W26
.01 seconds
alter table T1 drop partition P_2022W27
.02 seconds
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds
…
…
…
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds
SELECT count(*) from USER_TAB_PARTITIONS
Where
table_name not like 'BIN$%'
8
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
AND
table_name not like 'BIN$%'
P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34
P_2022W35

Oracle dropping last PARTITION for a table

I'm trying to drop all partitions for a table and I'm running into the following error:
ORA-14083: cannot drop the only partition of a partitioned table ORA-06512:
I know I can drop the table and recreate it but I want to see if my procedure can be modified to use an EXCEPTION that sets the retention to nothing, drops the PARTITION and sets the INTERVAL back to its original value?
Any help and expertise would be greatly appreciated. Below is my test CASE.
CREATE OR REPLACE PROCEDURE ddl(p_cmd varchar2)
authid current_user
is
BEGIN
dbms_output.put_line(p_cmd);
execute immediate p_cmd;
END;
/
CREATE OR REPLACE PROCEDURE
drop_partition(
p_tab varchar2,
p_date date
) authid current_user
is
v_high_value date;
cursor v_cur is
select table_name,
partition_name,
high_value,
partition_position
from user_tab_partitions
where table_name = upper(p_tab);
begin
for v_rec in v_cur loop
execute immediate 'select ' || v_rec.high_value || ' from dual' into v_high_value;
if v_high_value <= trunc(p_date)
then
-- dbms_output.put_line ('partition ' || v_rec.partition_name || ' high value is ' || to_char(v_high_value,'mm/dd/yyyy'));
--ddl( 'ALTER TABLE ' || p_tab || ' DROP PARTITION FOR(DATE ' || TO_CHAR(v_high_value,'YYYY-MM-DD') || ')');
ddl('alter table '||p_tab||' drop partition '||v_rec.partition_name);
end if;
end loop;
END;
/
CREATE TABLE partition_retention
(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
TABLE_NAME VARCHAR2(30),
DAYS NUMBER(6),
CONSTRAINT
partition_retention_pk primary key (table_name));
/
INSERT into partition_retention(TABLE_NAME, DAYS)
WITH data as (
select 'T1', 0
from dual union all
select 'T3', 15
from dual union all
select 'T4', 10
from dual union all
select 'T5', 5
from dual)
SELECT * from data;
/
CREATE TABLE t1 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL (NUMTODSINTERVAL(7,'DAY'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2022-01-01','YYYY-MM-DD'))
);
/
INSERT into t1 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-02-01')
select dt from dt;
/
BEGIN
FOR td IN
(
SELECT table_name
, NVL (pr.days, 30) AS days
FROM user_part_tables pt
JOIN user_part_key_columns pkc ON pkc.name = pt.table_name
JOIN user_tab_cols tc USING (table_name, column_name)
LEFT JOIN partition_retention pr USING (table_name)
WHERE pkc.object_type = 'TABLE'
AND pt.partitioning_type = 'RANGE'
AND REGEXP_LIKE (tc.data_type, '^DATE$|^TIMESTAMP.*')
ORDER BY table_name
)
LOOP
drop_partition(
td.table_name,
trunc(sysdate-td.days)
);
END LOOP;
END;
/
Your question says "drop the last partition" which can be different to "drop the only partition"
As stated already in comments, you cannot drop the partition when only one partition is left. If you like to remove the data, then you can TRUNCATE the partition.
This PL/SQL Block should cover all your cases:
CREATE TABLE PARTITION_RETENTION (
TABLE_NAME VARCHAR2(30),
RETENTION INTERVAL DAY(3) TO SECOND(0) --> More generic than number of days
);
DECLARE
CANNOT_DROP_LAST_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_LAST_PARTITION, -14758);
CANNOT_DROP_ONLY_ONE_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_ONLY_ONE_PARTITION, -14083);
ts TIMESTAMP;
CURSOR TablePartitions IS
SELECT TABLE_NAME, PARTITION_NAME, p.HIGH_VALUE, t.INTERVAL, RETENTION, DATA_TYPE
FROM USER_PART_TABLES t
JOIN USER_TAB_PARTITIONS p USING (TABLE_NAME)
JOIN USER_PART_KEY_COLUMNS pk ON pk.NAME = TABLE_NAME
JOIN USER_TAB_COLS tc USING (TABLE_NAME, COLUMN_NAME)
JOIN PARTITION_RETENTION r USING (TABLE_NAME);
BEGIN
FOR aPart IN TablePartitions LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
IF ts < SYSTIMESTAMP - aPart.RETENTION THEN
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' DROP PARTITION '||aPart.PARTITION_NAME|| ' UPDATE GLOBAL INDEXES';
DBMS_OUTPUT.PUT_LINE('Dropped partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
DBMS_OUTPUT.PUT_LINE('Cannot drop the only partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME|| ' UPDATE GLOBAL INDEXES';
DBMS_OUTPUT.PUT_LINE('Truncated partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
WHEN CANNOT_DROP_LAST_PARTITION THEN
BEGIN
DBMS_OUTPUT.PUT_LINE('Drop last partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL ()';
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' DROP PARTITION '||aPart.PARTITION_NAME;
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
-- Depending on the order the "last" partition can be also the "only" partition at the same time
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
DBMS_OUTPUT.PUT_LINE('Cannot drop the only partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME|| ' UPDATE GLOBAL INDEXES';
DBMS_OUTPUT.PUT_LINE('Truncated partittion '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
END;
END;
END IF;
END LOOP;
END;

ORACLE STORED PROCEDURE CURSOR IS WORKING VERY SLOW

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.

Exchange/Move partitions between tables

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

oracle scheduler to rename interval partitions

I get some strange behaviour.
I have a procedure which changes in the names of partitions in a table.
I created a job which runs this procedure every 2 mins for testing.
First run goes very smooth and without any error so far.
However, from 2nd run I get the following error
"ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
ORA-06512: at "CDS_USER.RENAMING_PARTITIONS", line 17"
Yes, My table is live. But when I run my procedure explicitly withoug the scheduler, it runs perfectly fine. No issues with that. And every first run by the scheduler is also perfect.
Here is the code for scheduler
begin
bms_Scheduler.create_job(
job_name => 'drop_user1' ,
job_action => 'RENAMING_PARTITIONS'
job_action => 'DROP_PARTITIONS'
,start_date => SYSDATE ,
repeat_interval => 'freq=hourly;INTERVAL=7',bysecond=0;' ,
enabled => TRUE ,
comments => 'schduling drop job.');
END;
Here is the code for procedure
create or replace PROCEDURE RENAMING_PARTITIONS
AS
hv varchar2(9);
max_part VARCHAR2(9);
begin
select max(partition_position) into max_part from user_tab_partitions where table_name='DEMO';
for x in (select partition_name, high_value, partition_position
from user_tab_partitions
where table_name = 'DEMO' and partition_name like 'SYS%')
loop
if x.partition_position <> max_part THEN
execute immediate 'select to_char('||x.high_value||'-1,''YYYYMMDD'') from dual' into hv;
partition '||x.partition_name
--||' to DATE_'||hv);
execute immediate('alter table DEMO rename partition '||x.partition_name
||' to DATE_'||hv);
end if;
end loop;
end;
How can I fix this?
Don't do this. I know its inconvenient that Oracle won't follow a predefined partition naming schema for system created interval partitions (probably an enhancement to come in future), but altering the table every 2 min is not a good alternative by any stretch.
Luckily, you really only need to know the partition name in advance when specifying it in queries. The typical query would be something like:
select * from my_table partition (blah);
In 11g, you can use the "partition for" clause to workaround the system generated names, as follows (example for interval DATE partitions):
SQL> set display on
SQL> set linesize 200
SQL> drop table test_data
Table dropped.
SQL> create table test_data (
start_date DATE,
store_id NUMBER,
inventory_id NUMBER,
qty_sold NUMBER
)
PARTITION BY RANGE (start_date)
INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION part_01 values LESS THAN (TO_DATE('20130101','YYYYMMDD'))
)
Table created.
SQL> insert into test_data values (to_date('20121231', 'YYYYMMDD'), 1, 2, 1)
1 row created.
SQL> commit
Commit complete.
SQL> SELECT table_name, partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_DATA'
ORDER BY table_name, partition_name
TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------
TEST_DATA PART_01 TO_DATE(' 2013-01-01 00:00:00', 'SYYYY-MM-DD HH24:
MI:SS', 'NLS_CALENDAR=GREGORIAN')
1 row selected.
SQL> insert into test_data values (to_date('20130101', 'YYYYMMDD'), 1, 5, 8)
1 row created.
SQL> insert into test_data values (to_date('20130115', 'YYYYMMDD'), 2, 4, 5)
1 row created.
SQL> insert into test_data values (sysdate, 2, 3, 2)
1 row created.
SQL> commit
Commit complete.
SQL> SELECT table_name, partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_DATA'
ORDER BY table_name, partition_name
TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------
TEST_DATA PART_01 TO_DATE(' 2013-01-01 00:00:00', 'SYYYY-MM-DD HH24:
MI:SS', 'NLS_CALENDAR=GREGORIAN')
TEST_DATA SYS_P67 TO_DATE(' 2013-02-01 00:00:00', 'SYYYY-MM-DD HH24:
MI:SS', 'NLS_CALENDAR=GREGORIAN')
TEST_DATA SYS_P68 TO_DATE(' 2013-03-01 00:00:00', 'SYYYY-MM-DD HH24:
MI:SS', 'NLS_CALENDAR=GREGORIAN')
3 rows selected.
SQL> -- get data for January partition only
SQL> select * from test_data partition for (to_date('20130101', 'YYYYMMDD'))
START_DATE STORE_ID INVENTORY_ID QTY_SOLD
----------- ---------- ------------ ----------
01-JAN-2013 1 5 8
15-JAN-2013 2 4 5
2 rows selected.

Resources