Prevent Oracle indexes turn USABLE after INSERT - oracle

I have an interval partitioned table. I'd like to create a local index unusable by default to make all inserts work fast. I want manually turn indexes usable after insert. Currently every insert which creates new partition makes correspondend local index usable. How can I prevent it?
UPDATE: I'm working with Oracle 11.2
create table T (
part integer
,nm number
)
partition by range (part)
interval (1)
(partition empty values less than (1));
create index t_loc_idx on T (part, nm) local unusable
insert into T
select 1, rownum from user_tables
insert into T
select 2, rownum from user_tables
select index_name, partition_name, status from user_ind_partitions
where index_name = 'T_LOC_IDX'
What we can observe:
INDEX_NAME PARTITION_NAME STATUS
T_LOC_IDX EMPTY UNUSABLE
T_LOC_IDX SYS_P8744 USABLE
T_LOC_IDX SYS_P8745 USABLE

Keep interval partition local indexes unusable by creating a partial index and setting indexing off:
create table T (
part integer
,nm number
)
partition by range (part)
interval (1)
(partition empty values less than (1))
INDEXING OFF /* <-- New */;
create index t_loc_idx on T (part, nm) INDEXING PARTIAL /* <-- New */ local unusable ;
insert into T
select 1, rownum from user_tables where rownum <= 10;
insert into T
select 2, rownum from user_tables where rownum <= 10;
select index_name, partition_name, status from user_ind_partitions
where index_name = 'T_LOC_IDX';
New results:
INDEX_NAME PARTITION_NAME STATUS
========== ============== ======
T_LOC_IDX EMPTY UNUSABLE
T_LOC_IDX SYS_P643 UNUSABLE
T_LOC_IDX SYS_P644 UNUSABLE

Related

Overlapping of Index

As per my understanding Overlap of index is as follows:-
CREATE INDEX idx1 ON TabA ( Col1, Col2, Col3 );
CREATE INDEX idx2 ON TabA ( Col1, Col2 );
CREATE INDEX idx3 ON TabA ( Col1 DESC, Col2 DESC );
In the table structure above, the index idx1 is a superset (overlap) of the index idx2 and therefore redundant.So index1 is overlap of Index2.
Droping of such overlap indexes can improve performance.
But How can we find all the overlap index in entire schema of oracle 11gR2?
Can anybody please guide me?Thanks in advance.
You may query user_ind_columns ordering and concatenating columns by column_position and then do a self-join to do a comparison.
WITH indx AS ( SELECT table_name,index_name,LISTAGG(column_name,',') WITHIN GROUP(
ORDER BY column_position
) AS cols
FROM user_ind_columns a
GROUP BY table_name,index_name
)
SELECT a.table_name,a.index_name AS sup_index,b.index_name sub_indx,
a.cols as super_set,b.cols AS sub_set
FROM indx a
JOIN indx b ON a.table_name = b.table_name
AND a.index_name != b.index_name
where ','|| a.cols||',' like ',%'|| b.cols|| '%,';
Result
TABLE_NAME SUP_INDEX SUB_INDX SUPER_SET SUB_SET
TABA IDX1 IDX2 COL1,COL2,COL3 COL1,COL2
JOB_HISTORY JHIST_EMP_ID_ST_DATE_PK JHIST_EMPLOYEE_IX EMPLOYEE_ID,START_DATE EMPLOYEE_ID
2 rows selected.
Note: Oracle Database treats descending indexes as if they were function-based indexes and hence the column names can't be compared directly through this method.
Also note that this statement by you is disputable.
Droping of such overlap indexes (ie Index2) can improve performance.

Automatic list partitioning

I'm using Oracle 11g.
I have large table that I want to add partitions. My partition (and subpartition) key column is of VARCHAR2 type.
I know that there is an INTERVAL RANGE PARTITION that can be auto-created but I need to do the same thing with list partition. Is there a way to do that with VARCHAR2 type (which doesn't represent any date).
Example:
create table t1 (name varchar2(30), company varchar2(10), value number);
I want that for every new name there will be a new partition. Is there a way to do that without writing specific PL/SQL code that will check if a new partition is needed and will do the creation? Maybe with some pattern or something...
Automatic list partitioning was added in 12.2. So you need to upgrade to do this.
In the meantime, you can create a catch-all, default partition. Then split new values out as needed:
create table t (
c1 int
) partition by list ( c1 ) (
partition pdef values ( default )
);
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
PDEF default
insert into t values ( 1 );
insert into t values ( 2 );
alter table t
split partition pdef
values ( 1 ) into (
partition p1, partition pdef
);
alter table t
split partition pdef
values ( 2 ) into (
partition p2, partition pdef
);
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
P1 1
P2 2
PDEF default
When you get to 12.2, you can switch it to auto-list. But you need to drop the default partition first!
alter table t set partitioning automatic;
ORA-14852: SET [SUB]PARTITIONING AUTOMATIC is not legal on this table.
alter table t drop partition pdef;
alter table t set partitioning automatic;
insert into t values ( 3 );
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
P1 1
P2 2
SYS_P5201 3

Exchanging Range-Partitions with Local Indexes

We have tables with range partitions of a month interval. The indexes on the tables including the primary key are local indexes.
However, when exchanging the partition from these tables with a table without partition, the local indexes become UNUSABLE. In the Oracle documentation, it only talks about issues with Global indexes. So we converted the global primary indexes to local by adding the partitioned key to the index.
Example queries -
CREATE TABLE sourcetable
(owner varchar2(30), OBJECT_NAME VARCHAR2(100))
PARTITION BY RANGE (owner)
(PARTITION P1 VALUES LESS THAN (MAXVALUE));
CREATE TABLE DESTTABLE
(owner varchar2(30), OBJECT_NAME VARCHAR2(100));
create index sourcetable_idx
on sourcetable ( owner )
local ;
alter table sourcetable add constraint src_pk primary key ( owner );
insert into sourcetable select U.OBJECT_ID, u.object_name from user_objects u
where rownum <11;
select index_name,partition_name,status from user_ind_partitions
where index_name IN (
select index_name from user_indexes
where table_name = 'SOURCETABLE'
);
The select gives the index status as
USABLE
ALTER TABLE sourcetable
EXCHANGE PARTITION p1 WITH TABLE desttable;
select index_name,partition_name,status from user_ind_partitions
where index_name IN (
select index_name from user_indexes
where table_name = 'SOURCETABLE'
);
The select on index status after exchanging the partition gives the index status as
UNUSABLE
We want to do online partition exchange. Is there any way we can do the exchange using statements like
UPDATE INDEXES
with the
EXCHANGE PARTITION
statement?
We tried using EXCHANGE statement with UPDATE INDEXES, but the indexes still became UNUSABLE.
ALTER TABLE sourcetable
EXCHANGE PARTITION p1 WITH TABLE desttable UPDATE INDEXES;
UPDATE INDEXES applies only for global indexes. Since you don't have any, this clause has no effect.
You must use INCLUDING INDEXES. However, for this you must create the same indexes on source and destination table. Try this one:
CREATE INDEX desttable_idx ON desttable ( OWNER );
ALTER TABLE sourcetable EXCHANGE PARTITION p1 WITH TABLE desttable INCLUDING INDEXES;

Oracle find dependent columns of function based index

How can I create a query to return the user columns that are referenced from an Oracle function-based index?
Eg. I create an index such as:
CREATE INDEX MY_INDEX ON MY_TABLE (SQRT(MY_COL));
For non-function based indexes I just query ALL_IND_COLUMNS such as:
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM ALL_IND_COLUMNS
WHERE INDEX_NAME = 'MY_INDEX'
However, this query returns COLUMN_NAME of a system generated column such as 'SYS_NC00012$' instead of the actual table column 'MY_COL' that the function-based index uses.
I know I can query DBMS_METADATA and read the returned index creation DDL
SELECT DBMS_METADATA.GET_DDL('INDEX', 'MY_INDEX') AS QUERY_STRING FROM DUAL
but this means I have to parse the function text which is not reliable for my purposes.
Therefore, can anyone please point me in the right direction as to how I can precisely determine which actual table columns a function-based index is defined upon?
SQL> Create table my_table ( my_col int);
Table Created;
SQL> CREATE INDEX MY_INDEX ON MY_TABLE (SQRT(MY_COL));
Index Created
SQL> select
2 table_name c1,
3 index_name c2,
4 column_expression c3
5 from
6 dba_ind_expressions
7 where
8 table_name='MY_TABLE';
Table Index
name Name Expression
------------------------ ------------------------- ---------------------------------------
MY_TABLE MY_INDEX SQRT("MY_COL")
you can do some trick with "COMMENT ON COLUMN" , try this
Create table my_table ( my_col int);
CREATE INDEX MY_INDEX ON MY_TABLE (SQRT(my_col));
COMMENT ON COLUMN my_table.my_col IS 'MY_INDEX:my_col'; -- add your index:col
select * from ALL_COL_COMMENTS where table_NAME ='MY_TABLE'
you can use this trick function based index.

Oracle: how to drop a subpartition of a specific partition

I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...

Resources