Oracle: Range-Range Interval Partition/Subpartition - oracle

I need to create a range-partitioned table:
i.e.
create table table1(item_id number(22), sys_entry_date timestamp default sysdate)
partition by range(sys_entry_date) interval(NUMTOYMINTERVAL(1,'YEAR'))
(partition p01 values less than (to_date('31-DEC-2016','DD-MON-YYYY')));
A few insert for demo purpose:
---Should lie in the main partition's main subpartition (crnt_part) since the it's part of the latest records received.
insert into table1 values(1, sysdatetime);
---Should lie in the main partition's subpartition of default section (prev_part) since the it's 2 days older
insert into table1 values(1, sysdatetime-3);
insert into table1 values(1, sysdatetime-4);
---Would help us identify the yearly partitions (suggestive)
insert into table1 values(2, sysdatetime-1500);
insert into table1 values(3, sysdatetime-1200);
insert into table1 values(4, sysdatetime-800);
insert into table1 values(1, sysdatetime-400);
I want to achieve following through it:
An Yearly partition;
Within the yearly, a sub-partition based on sys_entry_date which
2.a. latest 2 days held in crnt_part
2.b. remaining held in default partition, maybe in prev_part
Appreciate if someone could help in this particular context.

Related

Insert the data of particular column in oracle

I have a table named as T1 with few columns. Out of these columns, one column name is INSERT_DATE and its datatype is TIMESTAMP(3). I want to update the INSERT_DATE column with data "ABC" in all rows.
CREATE TABLE T1(NAME VARCHAR2(5), INSERT_DATE TIMESTAMP(3));
INSERT INTO T1 VALUES('NAVIN',CURRENT_TIMESTAMP);
INSERT INTO T1 VALUES('KAVIN',CURRENT_TIMESTAMP);
INSERT INTO T1 VALUES('TAVIN',CURRENT_TIMESTAMP);
I have queried in oracle like
UPDATE T1
SET INSERT_DATE = 'ABC';
COMMIT;
It is not getting updated. Is there anything I need to add a code.

Oracle interval partitions are not created automatically with alter sub-partition template

I am using oracle 12c Interval partitioning. I have created the range partition with 1 month interval and list sub-partition using a unique identifier (lets say LOGIN_INTFID).
In table DDL I have added the list of sub-partitions that were known to me at the time of table creation. Here is extract of table DDL:
CREATE TABLE TEST
(
UNIQUE_ID NUMBER(9) NOT NULL,
LOGIN_INTFID VARCHAR2(20) NOT NULL,
LOGIN_SEQNO NUMBER(15) NOT NULL,
LOGIN_DATE DATE DEFAULT SYSDATE NOT NULL
)
PARTITION BY RANGE (LOGIN_DATE)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
SUBPARTITION BY LIST (LOGIN_INTFID) SUBPARTITION TEMPLATE (
SUBPARTITION SP1 VALUES ('ABC'),
SUBPARTITION SP2 VALUES ('DEF'),
)
(PARTITION TEST_Y2018M7D1 VALUES LESS
THAN (TO_DATE('2018-07-01 23:59:59', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')))
;
With this, new partition and sub partitions are created successfully. Later on, I had added one more sub-partition using the following alter command:
ALTER TABLE TEST modify partition SYS_P7068 add subpartition SP3 values ('XYZ');
I have also altered table TEST with the intention that next time when a new partition gets created, this new sub-partition is included in the table automatically:
ALTER TABLE TEST SET SUBPARTITION TEMPLATE (SUBPARTITION SP3 VALUES('XYZ'));
However, this last part is not working as expected. New partitions are not getting created when I am trying to insert data containing XYZ sub partition value. New partitions are getting created only when sub-partition values ABC/DEF are inserted.
What am I doing wrong?
You can't add a new subpartition to a template.
As the documetation states:
You can modify a subpartition template of a composite partitioned table by replacing it with a new subpartition template.
You have to define a new template consisting of the old and new subpartitions.
This will be valid for the partitions not yet created, for existing partition you have to add the subpartitions manually.
Example - after creation of your table you get one partition with two subpartitions
select PARTITION_NAME, SUBPARTITION_NAME,HIGH_VALUE
from user_tab_subpartitions where table_name = 'TEST';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- ----------------- ----------
TEST_Y2018 TEST_Y2018M7D1_ 'ABC'
TEST_Y2018 TEST_Y2018M7D1_ 'DEF'
Inserting a row adds an other partition with the same two subpartitions:
insert into test (UNIQUE_ID,LOGIN_INTFID,LOGIN_SEQNO,LOGIN_DATE)
values(1,'ABC',1,DATE'2018-08-02');
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- -------------------- ----------
TEST_Y2018 TEST_Y2018M7D1_SP1 'ABC'
TEST_Y2018 TEST_Y2018M7D1_SP2 'DEF'
SYS_P14654 SYS_SUBP14652 'ABC'
SYS_P14654 SYS_SUBP14653 'DEF'
Now you change the subpartition template - by defining all the new subspartitions
ALTER TABLE TEST SET SUBPARTITION TEMPLATE (
SUBPARTITION SP1 VALUES ('ABC'),
SUBPARTITION SP2 VALUES ('DEF'),
SUBPARTITION SP3 VALUES('XYZ'));
and adds an other row
insert into test (UNIQUE_ID,LOGIN_INTFID,LOGIN_SEQNO,LOGIN_DATE)
values(1,'ABC',1,DATE'2018-09-02');
The new partition has now as expected three subpartitions
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- -------------------- ----------
TEST_Y2018 TEST_Y2018M7D1_SP1 'ABC'
TEST_Y2018 TEST_Y2018M7D1_SP2 'DEF'
SYS_P14654 SYS_SUBP14652 'ABC'
SYS_P14654 SYS_SUBP14653 'DEF'
SYS_P14658 SYS_SUBP14655 'ABC'
SYS_P14658 SYS_SUBP14656 'DEF'
SYS_P14658 SYS_SUBP14657 'XYZ'

Create partition in an indexed table

I have a table which holds data for 12 hours. Every 5 minutes, it keeps deleting data which is more than 12 hours old and adds new data. It has almost 15-20 million rows. I want to create partition by hour and also index the table on column(time_stamp), to make the row fetching faster.
I will obviously do interval or range partitioning, but found that interval partitioning doesn't work on indexed table. So please help me with the syntax so that oracle creates 12 partitions and automatically adds new one when new time_stamp data is added which is after first 12 hours. I have already got a procedure to delete oldest partition which i will use so that there is always 12 hours of data.
I am stating the columns below.
CustomerId,ApplicationId,Time_Stamp,Service
I have tried to come up with this, but don't know how it will create new partitions
CREATE TABLE local_table
(customerid VARCHAR2(30),
applicationid VARCHAR2(30),
time_stamp TIMESTAMP,
service VARCHAR2(30))
PARTITION BY RANGE(time_stamp)
(
PARTITION t1 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 00:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t2 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 01:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t3 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 02:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t4 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 03:00:00.0','YYYY-MM- DD HH24:MI:SS.ff')),
PARTITION t5 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 04:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t6 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 05:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t7 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 06:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t8 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 07:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t9 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 08:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t10 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 09:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t11 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 10:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t12 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 11:00:00.0','YYYY-MM-DD HH24:MI:SS.ff'))
);
create index index_time_stamp on local_table(TIME_STAMP);
I am using- Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit
Create table with autopartitiong and LOCAL (partitioning) index.
The local_partitioned_index clauses let you specify that the index is partitioned on the same columns, with the same number of partitions and the same partition bounds as table. Oracle Database automatically maintains local index partitioning as the underlying table is repartitioned.
CREATE TABLE local_table
(customerid VARCHAR2(30),
applicationid VARCHAR2(30),
time_stamp TIMESTAMP,
service VARCHAR2(30))
PARTITION BY RANGE(time_stamp)
INTERVAL(NUMTODSINTERVAL(1, 'HOUR'))
(PARTITION t1 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 00:00:00.0','YYYY-MM-DD HH24:MI:SS.ff'))
);
CREATE INDEX index_time_stamp on local_table(TIME_STAMP) LOCAL;
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('1', 'a', sysdate, 'b');
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('2', 'c', sysdate + 1/1440, 'd');
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('3', 'e', sysdate + 1/24, 'f');
SELECT *
FROM user_tab_partitions;
The INTERVAL clause of the CREATE TABLE statement establishes interval
partitioning for the table. You must specify at least one range
partition using the PARTITION clause. The range partitioning key value
determines the high value of the range partitions, which is called the
transition point, and the database automatically creates interval
partitions for data beyond that transition point. The lower boundary
of every interval partition is the non-inclusive upper boundary of the
previous range or interval partition.
For example, if you create an interval partitioned table with monthly
intervals and the transition point at January 1, 2010, then the lower
boundary for the January 2010 interval is January 1, 2010. The lower
boundary for the July 2010 interval is July 1, 2010, regardless of
whether the June 2010 partition was previously created. Note, however,
that using a date where the high or low bound of the partition would
be out of the range set for storage causes an error. For example,
TO_DATE('9999-12-01', 'YYYY-MM-DD') causes the high bound to be
10000-01-01, which would not be storable if 10000 is out of the legal
range.
Some quick DRAFT for your second question about DROP PARTITION. Examine and debug before uncomment ALTER TABLE. You can create scheduler job for run this block of code every hour.
DECLARE
l_pt_cnt NUMBER;
l_pt_name VARCHAR2(100);
l_minrowid ROWID;
l_mindate TIMESTAMP;
BEGIN
-- get partition count
SELECT count(*)
INTO l_pt_cnt
FROM user_tab_partitions
WHERE table_name = 'LOCAL_TABLE';
IF l_pt_cnt > 12 THEN
SELECT min(time_stamp)
INTO l_mindate
FROM LOCAL_TABLE;
-- get ROWID with min date
SELECT min(rowid)
INTO l_minrowid
FROM LOCAL_TABLE
WHERE time_stamp = l_mindate;
-- get name of partition with row with min date
SELECT subobject_name
INTO l_pt_name
FROM LOCAL_TABLE
JOIN user_objects
ON dbms_rowid.rowid_object(LOCAL_TABLE.rowid) = user_objects.object_id
WHERE LOCAL_TABLE.rowid = l_minrowid;
DBMS_OUTPUT.put_line('ALTER TABLE LOCAL_TABLE DROP PARTITION ' || l_pt_name );
--EXECUTE IMMEDIATE 'ALTER TABLE LOCAL_TABLE DROP PARTITION ' || l_pt_name;
END IF;
END;

two triggers on one table

I am very new to oracle database my office is using oracle 10g. My question is
I have two tables one is current_cases having columns as case_id, col1, col2 col3..... another table backup_cases have backup_id, case_id, col1,col2,col3...
where case_id of current_cases is the same as case_id of backup_cases
I would like to create a trigger before update current_cases to insert all row the data into backup_cases, but there is already one more trigger on backup_cases to insert backup_sequence next value. Then how to create the update trigger, will the nextval trigger on backup_cases will automatically fill or should I over ride and take the sequence.next val an insert into the backup_cases. please give some idea about this small problem.....
...will the nextval trigger on backup_cases will automatically fill?
Trigger on backup_cases will work, but you must explicitly list all the inserted values, not this way: insert ... select * ....
Test: (everything is simplified, no primary keys, indices, foreign keys, constraints, just to address your question in short, readable way):
-- tables creation
create table current_cases (case_id number, col1 varchar2(20),
col2 varchar2(20));
create table backup_cases (backup_id number, case_id number, col1 varchar2(20),
col2 varchar2(20));
-- sequences creation
create sequence cc_seq;
create sequence bc_seq;
-- triggers
create or replace trigger bc_trg before insert on backup_cases
for each row
begin
select bc_seq.nextval into :new.backup_id from dual;
end;
create or replace trigger cc_trg before insert or update on current_cases
for each row
begin
if inserting then
select cc_seq.nextval into :new.case_id from dual;
else
insert into backup_cases (case_id, col1, col2)
values (:old.case_id, :old.col1, :old.col2);
end if;
end;
-- inserts and update sample data
insert into current_cases (col1, col2) values ('a1', 'a1');
insert into current_cases (col1, col2) values ('b1', 'b1');
insert into current_cases (col1, col2) values ('c1', 'c1');
update current_cases set col1 = 'b2a', col2='b2b' where case_id=2;
Results:
select * from current_cases;
CASE_ID COL1 COL2
---------- -------------------- --------------------
1 a1 a1
2 b2a b2b
3 c1 c1
select * from backup_cases;
BACKUP_ID CASE_ID COL1 COL2
---------- ---------- -------------------- --------------------
1 2 b1 b1
It doesn't look like you have anything to worry about. I assume there is an insert trigger on the backup table to generate the backup ID. I also assume there is an insert trigger on the current table to insert the incoming row also to the backup table. It may also be generating the current ID.
If you add an update trigger on the current table, it can write the NEW row to the backup table and everything should work normally. You don't need to make any changes to any existing trigger on either table.
If you have any doubts, this is a very easy operation to test.

Oracle Equivalent to MySQL INSERT IGNORE?

I need to update a query so that it checks that a duplicate entry does not exist before insertion. In MySQL I can just use INSERT IGNORE so that if a duplicate record is found it just skips the insert, but I can't seem to find an equivalent option for Oracle. Any suggestions?
If you're on 11g you can use the hint IGNORE_ROW_ON_DUPKEY_INDEX:
SQL> create table my_table(a number, constraint my_table_pk primary key (a));
Table created.
SQL> insert /*+ ignore_row_on_dupkey_index(my_table, my_table_pk) */
2 into my_table
3 select 1 from dual
4 union all
5 select 1 from dual;
1 row created.
Check out the MERGE statement. This should do what you want - it's the WHEN NOT MATCHED clause that will do this.
Do to Oracle's lack of support for a true VALUES() clause the syntax for a single record with fixed values is pretty clumsy though:
MERGE INTO your_table yt
USING (
SELECT 42 as the_pk_value,
'some_value' as some_column
FROM dual
) t on (yt.pk = t.the_pke_value)
WHEN NOT MATCHED THEN
INSERT (pk, the_column)
VALUES (t.the_pk_value, t.some_column);
A different approach (if you are e.g. doing bulk loading from a different table) is to use the "Error logging" facility of Oracle. The statement would look like this:
INSERT INTO your_table (col1, col2, col3)
SELECT c1, c2, c3
FROM staging_table
LOG ERRORS INTO errlog ('some comment') REJECT LIMIT UNLIMITED;
Afterwards all rows that would have thrown an error are available in the table errlog. You need to create that errlog table (or whatever name you choose) manually before running the insert using DBMS_ERRLOG.CREATE_ERROR_LOG.
See the manual for details
I don't think there is but to save time you can attempt the insert and ignore the inevitable error:
begin
insert into table_a( col1, col2, col3 )
values ( 1, 2, 3 );
exception when dup_val_on_index then
null;
end;
/
This will only ignore exceptions raised specifically by duplicate primary key or unique key constraints; everything else will be raised as normal.
If you don't want to do this then you have to select from the table first, which isn't really that efficient.
Another variant
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
where NOT EXISTS (select 1
from my_table a
where a.student_id = p.studentid
and a.group_id = g.groupid)
or you could do
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
MINUS
select student_id, group_id
from my_table
A simple solution
insert into t1
select from t2
where not exists
(select 1 from t1 where t1.id= t2.id)
This one isn't mine, but came in really handy when using sqlloader:
create a view that points to your table:
CREATE OR REPLACE VIEW test_view
AS SELECT * FROM test_tab
create the trigger:
CREATE OR REPLACE TRIGGER test_trig
INSTEAD OF INSERT ON test_view
FOR EACH ROW
BEGIN
INSERT INTO test_tab VALUES
(:NEW.id, :NEW.name);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END test_trig;
and in the ctl file, insert into the view instead:
OPTIONS(ERRORS=0)
LOAD DATA
INFILE 'file_with_duplicates.csv'
INTO TABLE test_view
FIELDS TERMINATED BY ','
(id, field1)
How about simply adding an index with whatever fields you need to check for dupes on and say it must be unique? Saves a read check.
yet another "where not exists"-variant using dual...
insert into t1(id, unique_name)
select t1_seq.nextval, 'Franz-Xaver' from dual
where not exists (select 1 from t1 where unique_name = 'Franz-Xaver');

Resources