oracle scheduler to rename interval partitions - oracle

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.

Related

How to add current hour and minutes to inserted date

I'm inserting data into table with date value, how to add to inserting date value current system time in format HH:MM
My insert
INSERT INTO tab1 VALUES to_date('11-OCT-2021');
I wanna insert '11-OCT-2021 22:08' where 22:08 current system time. How to do it
Thx
You can use:
INSERT INTO tab1 (column_name)
VALUES ( DATE '2021-10-11' + (SYSDATE - TRUNC(SYSDATE)) );
or
INSERT INTO tab1 (column_name)
VALUES (
TO_DATE(
'11-OCT-2021' || TO_CHAR(SYSDATE, 'HH24:MI:SS'),
'DD-MON-YYYYHH24:MI:SS',
'NLS_DATE_LANGUAGE=American'
)
);
db<>fiddle here
Here's one option:
SQL> create table tab1 (datum date);
Table created.
SQL> insert into tab1 values (date '2021-10-11');
1 row created.
SQL> select * from tab1;
DATUM
-------------------
11.10.2021 00:00:00
SQL> update tab1 set datum = to_date(to_char(datum, 'dd.mm.yyyy') || to_char(sysdate, 'hh24:mi'), 'dd.mm.yyyy hh24:mi');
1 row updated.
SQL> select * from tab1;
DATUM
-------------------
11.10.2021 21:47:00
SQL>
On the other hand, why wouldn't you insert the "whole" value immediately?
SQL> rollback;
Rollback complete.
SQL> insert into tab1 values (sysdate);
1 row created.
SQL> select * from tab1;
DATUM
-------------------
12.10.2021 21:48:21
SQL>

How to get date range partition names on a table between two dates (Given date range)

I am a developer. In my table, I had a date range partitions.
I want to get partition names which are defined for a table between two dates.
I tried with below query and it is returning all the partitions on a table.
select * from USER_TAB_PARTITIONS WHERE TABLE_NAME = 'TABLE NAME' ORDER BY PARTITION_NAME;
My requirement is , I will pass two dates as inputs and between those two dates i want to get partition names.
Please suggest query.
That's not very simple; the major obstacle is user_tab_partitions.high_value datatype, which is long, and it is difficult to work with. Usually you get
ORA-00932: inconsistent datatypes: expected - got LONG
error.
However, using a few steps, it can be done. Have a look at this example.
Create a partitioned table and insert a few rows into it:
SQL> CREATE TABLE test_part
2 (
3 datum DATE,
4 text VARCHAR2 (10)
5 )
6 PARTITION BY RANGE (datum)
7 INTERVAL ( NUMTODSINTERVAL (1, 'day') )
8 (PARTITION p0 VALUES LESS THAN (TO_DATE ('01.01.2020', 'dd.mm.yyyy')));
Table created.
SQL> INSERT INTO test_part
2 SELECT DATE '2015-08-15', 'Little' FROM DUAL
3 UNION ALL
4 SELECT DATE '2020-03-26', 'Foot' FROM DUAL;
2 rows created.
What does user_tab_partitions say?
SQL> SELECT table_name, partition_name, high_value
2 FROM user_tab_partitions
3 WHERE table_name = 'TEST_PART';
TABLE_NAME PARTITION_NAME HIGH_VALUE
--------------- --------------- -----------------------------------
TEST_PART P0 TO_DATE(' 2020-01-01 00:00:00', 'SY
YYY-MM-DD HH24:MI:SS', 'NLS_CALENDA
R=GREGORIA
TEST_PART SYS_P63 TO_DATE(' 2020-03-27 00:00:00', 'SY
YYY-MM-DD HH24:MI:SS', 'NLS_CALENDA
R=GREGORIA
So, you'd want to extract date part from the high_value column. The first step is kind of a stupid one - create a new table; basically CTAS:
SQL> CREATE TABLE temp_utp
2 AS
3 SELECT table_name, partition_name, TO_LOB (high_value) high_value
4 FROM user_tab_partitions;
Table created.
For simplicity (in further steps), I'll create a view based on that table which will extract date value (line #5):
SQL> CREATE OR REPLACE VIEW v_utp
2 AS
3 SELECT table_name,
4 partition_name,
5 TO_DATE (SUBSTR (high_value, 12, 10), 'rrrr-mm-dd') datum
6 FROM temp_utp;
View created.
The rest is easy now:
SQL> SELECT *
2 FROM v_utp
3 WHERE datum < DATE '2020-02-15';
TABLE_NAME PARTITION_NAME DATUM
--------------- --------------- ----------
TEST_PART P0 2020-01-01
SQL>
OK, you'd use two date parameters which would then lead to between in the final query, but that's easy to modify.
Major drawback here is CTAS which creates temp_utp table; you'd have to recreate it as frequently as you add new partitions into the main table. One option is to do it in a scheduled manner, e.g. using a database job (see dbms_job and/or dbms_scheduler documentation if you don't know how) which would schedule a stored procedure which will then use dynamic SQL, i.e. execute immediate to create temp_utp. You don't have to recreate a view - it will become valid as soon as a new temp_utp table is created.
I was trying to find solution for the same problem and found that creating a function to convert high value to date works fine like below
CREATE OR REPLACE FUNCTION get_high_value_date(p_partition_name IN VARCHAR2)
RETURN DATE
IS
l_varchar_date VARCHAR2(4000);
l_date DATE;
BEGIN
EXECUTE IMMEDIATE 'SELECT high_value FROM all_tab_partitions WHERE partition_name = :1 ' INTO l_varchar_date
USING p_partition_name;
EXECUTE IMMEDIATE 'SELECT '||l_varchar_date||' FROM dual' INTO l_date;
RETURN l_date;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ( 'Exc: '||SQLERRM );
RETURN NULL;
END;
Then you can use this for getting date value
SELECT get_high_value_date (partition_name) partition_date, partition_name
FROM all_tab_partitions
WHERE table_name = :table

Bulk Update in Oracle PL/SQL

I have a table like this:
table_name SQL_statement id_value result
employee select ... where ..= :1 101
customer select ....where ..= :1 903
there will be about 6-8 different tables
the ID_VALUE is supplied as parameter to SQL_Statement
the SQL statement will always return a single row.
the result of "c" above should be stored in result column
I know I can use EXECUTE IMMEDIATE, but that will create hard parsing in SGA.
What kind of BULK technique can be used here ?
If you can assume that the statement always extracts a single value with a fixed alias, you can try the following.
setup:
create table yourTable(table_name varchar2(30), SQL_statement varchar2(100), id_value number, result number);
insert into yourTable
(
select 'employee', 'select :1 *2 as result from dual', 1, null from dual union all
select 'customer', 'select :1 as result from dual', 10, null from dual
);
Given the alias result, you can use a single update statement.
update:
update yourTable
set result = extractvalue(xmltype(dbms_xmlgen.getxml(replace (SQL_statement, ':1', id_value))),'/ROWSET/ROW/RESULT')
result:
SQL> select *
2 from yourTable;
TABLE_NAME SQL_STATEMENT ID_VALUE RESULT
---------- ---------------------------------------- ---------- ----------
employee select :1 *2 as result from dual 1 2
customer select :1 as result from dual 10 10

Trigger Issue : ORA-00905: missing keyword

I am trying to insert a record into a another table (tableb) once a certain condition is met after an insert into a certain table (tablea)
So i have created a trigger that checks does the above,
condition : after inserting into tablea, check whether the sum of price in tablea is greater than a certain value, if it is, then insert the tino into tableb.
Script below will recreate the issue i am currently facing.. Need another paid of eyes on this.
-- create the tables
CREATE TABLE tablea
(
tino NUMBER not null,
price VARCHAR2(200),
dated date
)
partition by range (DATED)
(
partition PART_201608 values less than (TO_DATE(' 2016-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
partition PART_201609 values less than (TO_DATE(' 2016-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
partition PART_201610 values less than (TO_DATE(' 2016-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
)
--INSERT VALUES
INSERT INTO tablea (tino,price,dated)VALUES('1234567',10,SYSDATE);
COMMIT;
INSERT INTO tablea (tino,price,dated)VALUES('1234560',20,SYSDATE);
COMMIT;
-- create table table which once condition is met,, data is written into
CREATE TABLE tableb(tino number);
-- CREATE THE TRIGGER
CREATE OR REPLACE TRIGGER trg1
AFTER INSERT
ON tablea
FOR EACH ROW
DECLARE
v_price NUMBER;
v_partition VARCHAR2(20) := 'PART_'||TO_CHAR(SYSDATE,'YYYYMM');
v_tino VARCHAR2(20) := :NEW.tino;
sql_smt VARCHAR2(1000) := '';
BEGIN
sql_smt :='
SELECT SUM(price) price INTO v_price
FROM tablea PARTITION('||v_partition||')
WHERE tino = '||''''||v_tino||''''||'';
BEGIN
EXECUTE IMMEDIATE sql_smt;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
DBMS_OUTPUT.PUT_LINE('QUERY='|| sql_smt);
END;
--DBMS_OUTPUT.PUT_LINE('PRICE =' || v_price);
IF v_price >= 15 THEN
INSERT INTO tableb (tino) VALUES (v_tino);
COMMIT;
END IF;
END;
-- test the trigger
INSERT INTO tablea(tino,price,dated) VALUES('1234567',10,sysdate);
COMMIT;
Should return an ORA-00905: missing keyword
DBMS_OUTPUT.PUT_LINE(SQLERRM);
Returns :
SELECT SUM(price) price INTO v_price
FROM tablea PARTITION(PART_201609)
WHERE tino = '1234567'
Which should ran with no issues
Any pointers??
INTO v_price should be part of the execute immediate statement, not part of the dynamic SQL.
btw I think
WHERE tino = '||''''||v_tino||''''||'';
can be simplified to
WHERE tino = '''||v_tino||'''';
Or even better,
WHERE tino = :tino';
with v_tino passed as a bind variable with something like
execute immediate xyz into v_price using v_tino;

Where can I find the range interval partition tablespaces in the data dictionary?

For a range interval partitioned table, you can specify multiple tablespaces like:
CREATE TABLE range_part_interval_table(col1 NUMBER, col2 NUMBER)
PARTITION BY RANGE (col1)
INTERVAL (10) STORE IN (ts2, ts3, ts4)
(PARTITION VALUES LESS THAN (100) TABLESPACE ts1);
But I can't find where the interval tablespaces are stored in the data dictionary (here, ts2, ts3, ts4). Is this information available somewhere?
This should get you the info you need:
SELECT table_owner, table_name, partition_name, tablespace_name
FROM dba_tab_partitions
WHERE table_name = <table_name>;
The following is the table description:
%> desc dba_tab_partitions
Name Null Type
------------------------------ -------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TABLE_OWNER VARCHAR2(30)
TABLE_NAME VARCHAR2(30)
COMPOSITE VARCHAR2(3)
PARTITION_NAME VARCHAR2(30)
SUBPARTITION_COUNT NUMBER
HIGH_VALUE LONG()
HIGH_VALUE_LENGTH NUMBER
PARTITION_POSITION NUMBER
TABLESPACE_NAME VARCHAR2(30)
PCT_FREE NUMBER
PCT_USED NUMBER
INI_TRANS NUMBER
MAX_TRANS NUMBER
INITIAL_EXTENT NUMBER
NEXT_EXTENT NUMBER
MIN_EXTENT NUMBER
MAX_EXTENT NUMBER
MAX_SIZE NUMBER
PCT_INCREASE NUMBER
FREELISTS NUMBER
FREELIST_GROUPS NUMBER
LOGGING VARCHAR2(7)
COMPRESSION VARCHAR2(8)
COMPRESS_FOR VARCHAR2(18)
NUM_ROWS NUMBER
BLOCKS NUMBER
EMPTY_BLOCKS NUMBER
AVG_SPACE NUMBER
CHAIN_CNT NUMBER
AVG_ROW_LEN NUMBER
SAMPLE_SIZE NUMBER
LAST_ANALYZED DATE
BUFFER_POOL VARCHAR2(7)
GLOBAL_STATS VARCHAR2(3)
USER_STATS VARCHAR2(3)
Your interval partitions don't exist until you have some data in them....
SQL> CREATE TABLE range_part_interval_table(col1 NUMBER, col2 NUMBER)
2 PARTITION BY RANGE (col1)
3 INTERVAL (10) STORE IN (ts2, ts3, ts4)
4 (PARTITION VALUES LESS THAN (100) TABLESPACE ts1);
Table created.
SQL> SELECT table_owner, table_name, partition_name, tablespace_name
2 FROM dba_tab_partitions
3 WHERE table_name = 'RANGE_PART_INTERVAL_TABLE'
4 /
TABLE_OWNER TABLE_NAME PARTITION_NAME TABLESPACE_NAME
----------- ------------------------------ -------------- ---------------
APC RANGE_PART_INTERVAL_TABLE SYS_P55 TS1
SQL>
The empty table has only the defined partition. But if we insert some data for different intervals....
SQL> insert into range_part_interval_table values (90, 8888)
2 /
1 row created.
SQL> insert into range_part_interval_table values (110, 8888)
2 /
1 row created.
SQL> insert into range_part_interval_table values (310, 8888)
2 /
1 row created.
SQL> insert into range_part_interval_table values (120, 8888)
2 /
1 row created.
SQL> SELECT table_owner, table_name, partition_name, tablespace_name
2 FROM dba_tab_partitions
3 WHERE table_name = 'RANGE_PART_INTERVAL_TABLE'
4 /
TABLE_OWNER TABLE_NAME PARTITION_NAME TABLESPACE_NAME
----------- ------------------------------ -------------- ---------------
APC RANGE_PART_INTERVAL_TABLE SYS_P58 TS2
APC RANGE_PART_INTERVAL_TABLE SYS_P55 TS1
APC RANGE_PART_INTERVAL_TABLE SYS_P56 TS4
APC RANGE_PART_INTERVAL_TABLE SYS_P57 TS3
SQL>
check sys.insert_tsn_list$

Resources