Hemant K Chitale

Subscribe to Hemant K Chitale feed
I am an Oracle Database Specialist in Singapore.
EU visitors : Please note that this site uses cookies.
get an rss feed of this blog at http://hemantoracledba.blogspot.com/feeds/posts/default?alt=rss
follow me on twitter : @HemantKChitale
Updated: 11 hours 11 min ago

Oracle Database Configurations using Docker and Vagrant

Fri, 2018-08-17 02:16
Oracle now makes available configurations for the Database (and other products) on both Docker and Vagrant via GitHub.

Good time to familiarize oneself with GitHub, Docker and/or Vagrant.


For the Docker configuration see : https://github.com/oracle/docker-images/tree/master/OracleDatabase


For the Vagrant configuration see : https://github.com/oracle/vagrant-boxes/tree/master/OracleDatabase


(Note : The Examples have been available on GitHub for quite some time at https://github.com/oracle/oracle-db-examples )



Categories: DBA Blogs

Partitioning -- 3a : Indexes on a Partitioned Table

Sun, 2018-08-12 06:35
Building on the case study of the Range Partitioned Table from the previous Blog Post, here are some Indexes.

SQL> select index_name, tablespace_name, partitioned, uniqueness
2 from user_indexes
3 where table_name = 'SALES_DATA'
4 /

INDEX_NAME TABLESPACE_NAME PAR UNIQUENES
------------------------------ ------------------------------ --- ---------
SYS_C0017514 HEMANT NO UNIQUE

SQL>
SQL> select column_name, column_position
2 from user_ind_columns
3 where index_name = 'SYS_C0017514'
4 /

COLUMN_NAME COLUMN_POSITION
------------------------------ ---------------
SALE_ID 1

SQL>


We have an Index automatically built for the Primary Key constraint (note that the Index was created in the user's DEFAULT Tablespace).  This Index is a Global, Non-Partitioned Index.   Therefore, any Partition Maintenance operation (for a non-empty Partition) on the table may set the Index UNUSABLE unless the UPDATE INDEXES clause is used.

SQL> select status from user_indexes
2 where index_name = 'SYS_C0017514'
3 /

STATUS
--------
VALID

SQL> alter table sales_data drop partition P_2016;

Table altered.

SQL> select status from user_indexes
2 where index_name = 'SYS_C0017514'
3 /

STATUS
--------
VALID

SQL> insert into sales_data
2 values (1001,to_date('01-FEB-2017','DD-MON-RR'),'ABC1001',1,10,1000);

1 row created.

SQL> commit;

Commit complete.

SQL> alter table sales_data drop partition P_2017;

Table altered.

SQL> select status from user_indexes
2 where index_name = 'SYS_C0017514'
3 /

STATUS
--------
UNUSABLE

SQL>
SQL> alter index SYS_C0017514 rebuild;

Index altered.

SQL> select status from user_indexes
2 where index_name = 'SYS_C0017514'
3 /

STATUS
--------
VALID

SQL>


When I dropped the *empty* P_2016 Partition, the Index remained valid.  However, when I dropped the *non-empty*  P_2017 Partition (even if it has a single row and irrespective of whether Table/Partition statistics have been gathered) without the UPDATE INDEXES clause, the Index became UNUSABLE.  An UNUSABLE Unique Index will not allow fresh inserts (into *any* Partition of the table).

Next, I attempt to create a Local Partitioned Index.  Such an Index has a Partition corresponding to each Table Partition.

SQL> create index sales_data_lcl_ndx_1
2 on sales_data (sale_date, invoice_number) local
3 /

Index created.

SQL> select partitioned
2 from user_indexes
3 where index_name = 'SALES_DATA_LCL_NDX_1'
4 /

PAR
---
YES

SQL> select partitioned, tablespace_name, status
2 from user_indexes
3 where index_name = 'SALES_DATA_LCL_NDX_1'
4 /

PAR TABLESPACE_NAME STATUS
--- ------------------------------ --------
YES N/A

SQL>
SQL> select partition_name, tablespace_name, status
2 from user_ind_partitions
3 where index_name = 'SALES_DATA_LCL_NDX_1'
4 order by partition_position
5 /

PARTITION_NAME TABLESPACE_NAME STATUS
------------------------------ ------------------------------ --------
P_2018 TBS_YEAR_2018 USABLE
P_2019 TBS_YEAR_2019 USABLE
P_2020 TBS_YEAR_2020 USABLE
P_MAXVALUE USERS USABLE

SQL>


A Local Partitioned Index is created with the LOCAL keyword in the CREATE INDEX statement.
For a Partitioned Index, the TABLESPACE_NAME and STATUS attributes carry no meaning at the Index level --- these have values for each Partition.

Note how the Index Partitions were created with the same Partition Name and Tablespace Name as the Table Partitions. Similarly, any Partition Maintenance operations (DROP, MERGE, SPLIT) at the Table level will automatically be applied to the Index, dropping/creating the corresponding Index Partition(s).

In my next post, I will look at two other types of Index definitions on a Partitioned Table  (Global Partitioned Index and Bitmap Index).




Categories: DBA Blogs

Partitioning -- 2 : Simple Range Partitioning -- by DATE

Thu, 2018-08-09 03:35
Range Partitioning allows you to separate a logical table into a number of distinct physical segments, each segment holding data that maps to a range of values.
(I encourage you to read the Introduction in the first post in this series)

The simplest and most common implementation is Range Partitioning by a DATE column.

SQL> l
1 create table sales_data
2 (sale_id number primary key,
3 sale_date date,
4 invoice_number varchar2(21),
5 customer_id number,
6 product_id number,
7 sale_value number)
8 partition by range (sale_date)
9 (partition P_2015 values less than (to_date('01-JAN-2016','DD-MON-YYYY'))
10 tablespace TBS_YEAR_2015,
11 partition P_2016 values less than (to_date('01-JAN-2017','DD-MON-YYYY'))
12 tablespace TBS_YEAR_2016,
13 partition P_2017 values less than (to_date('01-JAN-2018','DD-MON-YYYY'))
14 tablespace TBS_YEAR_2017,
15 partition P_2018 values less than (to_date('01-JAN-2019','DD-MON-YYYY'))
16 tablespace TBS_YEAR_2018,
17 partition P_2019 values less than (to_date('01-JAN-2020','DD-MON-YYYY'))
18 tablespace TBS_YEAR_2019,
19 partition P_MAXVALUE values less than (MAXVALUE)
20 tablespace USERS
21* )
SQL> /

Table created.

SQL>


Here, I have created each Partition in a separate tablespace.  Note that the Partition Key (SALE_DATE) does not have to be the same as the Primary Key (SALE_ID)

I have also created a MAXVALUE Partition  (Some DBAs/Developers may mistakenly assume this to be a *default* partition.  Range Partitioning, unlike List Partitioning, does not have the concept of a "default" partition.  This simply is the Partition for incoming rows that have Partition Key value that is higher than the last (highest) defined Partition Key Upper Bound (31-Dec-2019 23:59:59 in this case)).

I can look up the data dictionary for these partitions in this manner :

SQL> select partition_name, tablespace_name   
2 from user_tab_partitions
3 where table_name = 'SALES_DATA'
4 order by partition_position
5 /

PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P_2015 TBS_YEAR_2015
P_2016 TBS_YEAR_2016
P_2017 TBS_YEAR_2017
P_2018 TBS_YEAR_2018
P_2019 TBS_YEAR_2019
P_MAXVALUE USERS

6 rows selected.

SQL>


Partitions are ordered by Partition *Position*  not Name.

How do I add a new partition for data for the year 2020 ?  By "splitting" the MAXVALUE partition.

SQL> alter table sales_data                 
2 split partition P_MAXVALUE
3 at (to_date('01-JAN-2021','DD-MON-YYYY'))
4 into
5 (partition P_2020 tablespace TBS_YEAR_2020, partition P_MAXVALUE)
6 /

Table altered.

SQL>
SQL> select partition_name, high_value
2 from user_tab_partitions
3 where table_name = 'SALES_DATA'
4 order by partition_position
5 /

PARTITION_NAME HIGH_VALUE
------------------------------ ---------------------------------------------
P_2015 TO_DATE(' 2016-01-01 00:00:00', 'SYYYY-MM-DD
P_2016 TO_DATE(' 2017-01-01 00:00:00', 'SYYYY-MM-DD
P_2017 TO_DATE(' 2018-01-01 00:00:00', 'SYYYY-MM-DD
P_2018 TO_DATE(' 2019-01-01 00:00:00', 'SYYYY-MM-DD
P_2019 TO_DATE(' 2020-01-01 00:00:00', 'SYYYY-MM-DD
P_2020 TO_DATE(' 2021-01-01 00:00:00', 'SYYYY-MM-DD
P_MAXVALUE MAXVALUE

7 rows selected.

SQL>
SQL> l
1 select partition_name, tablespace_name
2 from user_tab_partitions
3 where table_name = 'SALES_DATA'
4* order by partition_position
SQL> /

PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P_2015 TBS_YEAR_2015
P_2016 TBS_YEAR_2016
P_2017 TBS_YEAR_2017
P_2018 TBS_YEAR_2018
P_2019 TBS_YEAR_2019
P_2020 TBS_YEAR_2020
P_MAXVALUE USERS

7 rows selected.

SQL>


Note that, irrespective of the data format I specify in the CREATE or SPLIT commands, Oracle presents the Upper Bound Date (HIGH_VALUE) in it's own format, using a Gregorian Calendar.

How do I remove an older partition ?

SQL> alter table sales_data
2 drop partition P_2015
3 /

Table altered.

SQL>


A DROP command is very simple.

In my next post, I will add Indexes to this table.



Categories: DBA Blogs

Partitioning -- 1 : Introduction

Thu, 2018-08-09 02:54
I am beginning a new series of Blog Posts on Partitioning in Oracle.  I plan to cover 11g and 12c.   I might add posts on changes in 18c  (which is really 12.2.0.2 currently)

First, this is my presentation at AIOUG Sangam 11
and this the corresponding article

This series of posts will have new examples, from the simple to the complex, not present in the above presentation / article.
Categories: DBA Blogs

18c (18.3) Installation On Premises

Thu, 2018-07-26 21:42
Documentation by @oraclebase  (Tim Hall) on installing 18c (18.3) On Premises on OEL :

Oracle Database 18c Installation On Oracle Linux 6 (OL6) and 7 (OL7)
.
.
.




 
Categories: DBA Blogs

Global Temporary Table -- revisited

Wed, 2018-06-27 09:00
Revisiting the previous case in a 12.2 PDB ....

(This time, the two sessions by "HEMANT" and "SYSTEM" have the Username as the SQL prompt)

In the previous blog post, I demonstrated how to check space allocation for a GTT.   But how does Oracle determine how much space to allocate ?

HEMANT>create global temporary table my_gtt_2
2 (id_number number, object_name varchar2(128))
3 on commit preserve rows
4 /

Table created.

HEMANT>
SYSTEM>select sid,serial# from v$session where username = 'HEMANT';

SID SERIAL#
---------- ----------
300 11923

SYSTEM>
SYSTEM>select username, session_num, sql_id, tablespace, contents, segtype, con_id, sql_id_tempseg
2 from v$tempseg_usage
3 /

no rows selected

SYSTEM>


So, the creation of a GTT does not allocate any space. A GTT definition is a logical definition and does not allocate space unless and until rows are inserted.

Let me insert a row and check the space.

HEMANT>insert into my_gtt_2 values (1, 'First Object');

1 row created.

HEMANT>commit;

Commit complete.

HEMANT>
SYSTEM>select username, session_num, sql_id, tablespace, contents, segtype, con_id, sql_id_tempseg
2 from v$tempseg_usage
3 /

USERNAME
--------------------------------------------------------------------------------------------------------------------------------
SESSION_NUM SQL_ID TABLESPACE CONTENTS SEGTYPE CON_ID SQL_ID_TEMPSE
----------- ------------- ------------------------------ --------- --------- ---------- -------------
HEMANT
11923 54zdzm1mrqpy9 TEMP TEMPORARY DATA 6 54zdzm1mrqpy9


SYSTEM>
SYSTEM>select sql_id, sql_text
2 from v$sql
3 where sql_id in ('54zdzm1mrqpy9')
4 /

SQL_ID
-------------
SQL_TEXT
------------------------------------------------------------------------------------------------------------------------------------
54zdzm1mrqpy9
insert into my_gtt_2 values (1, 'First Object')


SYSTEM>
SYSTEM>select extents, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=11923
4 /

EXTENTS BLOCKS SQL_ID SQL_ID_TEMPSE
---------- ---------- ------------- -------------
1 128 54zdzm1mrqpy9 54zdzm1mrqpy9

SYSTEM>


So, that is 1MB (128 blocks of 8KB each) for the initial extent.  Why is it so ?

SYSTEM>select extent_management, allocation_type, initial_extent, next_extent
2 from dba_tablespaces
3 where tablespace_name = 'TEMP'
4 /

EXTENT_MAN ALLOCATIO INITIAL_EXTENT NEXT_EXTENT
---------- --------- -------------- -----------
LOCAL UNIFORM 1048576 1048576

SYSTEM>


Because, by default, a TEMPORARY TABLESPACE is created with 1MB Uniform Extents.

Can I change this ?

SYSTEM>create temporary tablespace small_temp
2 tempfile '/usr/tmp/small_temp.dbf' size 100M
3 extent management local uniform size 64K;

Tablespace created.

SYSTEM>select extent_management, allocation_type, initial_extent, next_extent
2 from dba_tablespaces
3 where tablespace_name = 'SMALL_TEMP'
4 /

EXTENT_MAN ALLOCATIO INITIAL_EXTENT NEXT_EXTENT
---------- --------- -------------- -----------
LOCAL UNIFORM 65536 65536

SYSTEM>
HEMANT>create global temporary table small_gtt
2 (id_number number, object_name varchar2(128))
3 on commit preserve rows
4 tablespace small_temp
5 /

Table created.

HEMANT>
SYSTEM>select username, tablespace, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=11923
4 /

USERNAME
--------------------------------------------------------------------------------------------------------------------------------
TABLESPACE BLOCKS SQL_ID SQL_ID_TEMPSE
------------------------------ ---------- ------------- -------------
HEMANT
TEMP 128 fd8qcczn6avw6 54zdzm1mrqpy9


SYSTEM>
HEMANT>insert into small_gtt
2 values (1, 'First Object');

1 row created.

HEMANT>commit;

Commit complete.

HEMANT>
SYSTEM>select username, tablespace, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=11923
4 /

USERNAME
--------------------------------------------------------------------------------------------------------------------------------
TABLESPACE BLOCKS SQL_ID SQL_ID_TEMPSE
------------------------------ ---------- ------------- -------------
HEMANT
TEMP 128 2j3pja8qjx1sd 54zdzm1mrqpy9

HEMANT
SMALL_TEMP 8 2j3pja8qjx1sd 2j3pja8qjx1sd


SYSTEM>
SYSTEM>select sql_id, sql_text
2 from v$sql
3 where sql_id in ('54zdzm1mrqpy9','fd8qcczn6avw6','2j3pja8qjx1sd')
4 /

SQL_ID
-------------
SQL_TEXT
------------------------------------------------------------------------------------------------------------------------------------
2j3pja8qjx1sd
insert into small_gtt values (1, 'First Object')


SYSTEM>


(The previous two SQLs no longer present in the cache but we can see that "54zdzm1mrqpy9" is for the first GTT and "fd8qcczn6avw6" is for the second GTT)

Thus, my existing HEMANT session has two different Temporary Segment usages being reported. That in the TEMP tablespace is 1MB for the 1 row in MY_GTT_2 and that in the SMALL_TEMP tablespace is 64KB for the 1 row in SMALL_GTT.

If I TRUNCATE a GTT (or exit the session) space is released.

HEMANT>truncate table my_gtt_2;

Table truncated.

HEMANT>
SYSTEM>select username, tablespace, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=11923
4 /

USERNAME
--------------------------------------------------------------------------------------------------------------------------------
TABLESPACE BLOCKS SQL_ID SQL_ID_TEMPSE
------------------------------ ---------- ------------- -------------
HEMANT
SMALL_TEMP 8 0zwdmqw9fpkjv 2j3pja8qjx1sd


SYSTEM>


Thus you can have
(a) multiple GTTs (with different definitions, e.g. one for SALES data processing and one for HR data processing)
(b) in multiple TEMPORARY TABLESPACES

The GTT definitions are visible across all sessions that are in the same USER login or have been granted privileges but the data in one session is *not* visible to another session, even if COMMITTed (unlike normal "Permanent" Tables)
.
.
.


Categories: DBA Blogs

Global Temporary Table in a PDB

Sun, 2018-06-17 10:45
Where and how is the space consumption for a Global Temporary Table when created in a Pluggable Database ?

In a 12c MultiTenant Database, each Pluggable Database (PDB) has its own Temporary Tablespace. So, a GTT (Global Temporary Table) in a PDB is local to the associated Temporary Tablespace.

Let me be clear.  The "Global" does *not* mean that the table is
(a) available across all PDBs   (it is restricted to that PDB alone)
(b) available to all schemas (it is restricted to the owner schema alone, unless privileges are granted to other database users as well)
(c) data is visible to other sessions (data in a GTT is visible only to that session that populated it)

The "global" really means that the definition is created once and available across multiple sessions, each session having a "private" copy of the data.
The "temporary" means that the data does not persist.  If the table is defined as "on commit delete rows", rows are not visible after a COMMIT is issued.  If the table is defined as "on commit preserve rows", rows remain only for the life of the session.  In either case, a TRUNCATE can also be used to purge rows.


Here, I connect to a particular PDB and create a GTT and then populate it

$sqlplus hemant/hemant@PDBHKC

SQL*Plus: Release 12.2.0.1.0 Production on Sun Jun 17 23:06:28 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

Last Successful login time: Sun Jun 17 2018 23:02:29 +08:00

Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

SQL> create global temporary table my_gtt
2 (id_number number, object_name varchar2(128))
3 on commit preserve rows
4 /

Table created.

SQL>
$sqlplus hemant/hemant@PDBHKC

SQL*Plus: Release 12.2.0.1.0 Production on Sun Jun 17 23:06:28 2018

Copyright (c) 1982, 2016, Oracle. All rights reserved.

Last Successful login time: Sun Jun 17 2018 23:02:29 +08:00

Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

SQL> create global temporary table my_gtt
2 (id_number number, object_name varchar2(128))
3 on commit preserve rows
4 /

Table created.

SQL>
SQL> select distinct sid from v$mystat;

SID
----------
36

SQL>
SQL> select serial# from v$session where sid=36;

SERIAL#
----------
4882

SQL>


Another session can see that the table exists (without any corresponding "permanent" tablespace) but not see any data in it.

SQL> select temporary, tablespace_name
2 from user_tables
3 where table_name = 'MY_GTT'
4 /

T TABLESPACE_NAME
- ------------------------------
Y

SQL> select count(*) from my_gtt;

COUNT(*)
----------
0


Let's look for information on the Temporary Tablespace / Segment usage(querying from the second session)

SQL> select sid, serial#, sql_id    
2 from v$session
3 where username = 'HEMANT';

SID SERIAL# SQL_ID
---------- ---------- -------------
36 4882
300 34315 739nwj7sjgaxp

SQL> select username, session_num, sql_id, tablespace, contents, segtype, con_id, sql_id_tempseg
2 from v$tempseg_usage;

USERNAME SESSION_NUM SQL_ID TABLESPA CONTENTS SEGTYPE CON_ID SQL_ID_TEMPSE
-------- ----------- ------------- -------- --------- --------- ---------- -------------
HEMANT 4882 92ac4hmu9qgw3 TEMP TEMPORARY DATA 6 3t82sphjrt73h

SQL> select sql_id, sql_text
2 from v$sql
3 where sql_id in ('92ac4hmu9qgw3','3t82sphjrt73h');

SQL_ID
-------------
SQL_TEXT
------------------------------------------------------------------------------------------------------------------------------------
92ac4hmu9qgw3
select serial# from v$session where sid=36


SQL>


So, SID 36 is the session that populated the GTT and identified it's own SID (36) and SERIAL# (4882), which we can see as the user of the Temporary Segment when querying from the second session (SID 300).

What about the size of the temporary segment populated by SESSION_NUM (i..e SERIAL#)=4882 ?
Again, querying from the second session.

SQL> select extents, blocks, sql_id, sql_id_tempseg 
2 from v$tempseg_usage
3 where session_num=4882;

EXTENTS BLOCKS SQL_ID SQL_ID_TEMPSE
---------- ---------- ------------- -------------
4 512 92ac4hmu9qgw3 3t82sphjrt73h

SQL>


Now, let's "grow" the GTT with more rows (and then query from the other session).

SQL> insert into my_gtt select * from my_gtt;

72638 rows created.

SQL>
SQL> l
1 select extents, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3* where session_num=4882
SQL> /

EXTENTS BLOCKS SQL_ID SQL_ID_TEMPSE
---------- ---------- ------------- -------------
8 1024 gfkbdvpdb3qvf 3t82sphjrt73h

SQL> select sql_text from v$sql where sql_id = 'gfkbdvpdb3qvf';

SQL_TEXT
------------------------------------------------------------------------------------------------------------------------------------
insert into my_gtt select * from my_gtt

SQL>


So, the increased space allocation in the Temporary Segment is from the growth of the GTT. Let's grow it further.

SQL> INSERT INTO MY_GTT select * from MY_GTT;

145276 rows created.

SQL> /

290552 rows created.

SQL>
SQL> select extents, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=4882
4 /

EXTENTS BLOCKS SQL_ID SQL_ID_TEMPSE
---------- ---------- ------------- -------------
29 3712 2c3sccf0pj5g1 3t82sphjrt73h

SQL> select sql_text, executions from v$sql where sql_id = '2c3sccf0pj5g1';

SQL_TEXT
------------------------------------------------------------------------------------------------------------------------------------
EXECUTIONS
----------
INSERT INTO MY_GTT select * from MY_GTT
2


SQL>


So, the growth of the GTT results in increased space allocation in the Temporary Segment.

What happens if I truncate the GTT ?

SQL> truncate table my_gtt;

Table truncated.

SQL>
SQL> select extents, blocks, sql_id, sql_id_tempseg
2 from v$tempseg_usage
3 where session_num=4882;

no rows selected

SQL>
SQL> select * from v$tempseg_usage;

no rows selected

SQL>


Temp Space is released by the TRUNCATE of the GTT.

I invite you to try this with a GTT created with ON COMMIT DELETE ROWS and see what happens before and after the COMMIT.

.
.
.

Categories: DBA Blogs

New Video : Online Relocation of a Pluggable Database

Fri, 2018-06-08 23:09
I have published a new YouTube Video:    Online Relocation of a Pluggable Database.

.
.
.
Categories: DBA Blogs

New Video : BACKUP AS COPY PLUGGABLE DATABASE

Sun, 2018-05-20 09:42
I have published a new YouTube video on the RMAN "BACKUP AS COPY PLUGGABLE DATABASE" command.
.
.
.

Categories: DBA Blogs

Offline Relocation of a PDB using RMAN

Sun, 2018-05-06 11:17
I've published a new video on Offline Relocation of a PDB using RMAN in 12.2
.
.
.

Categories: DBA Blogs

Domain Indexes -- 4 : CTXRULE Index

Mon, 2018-04-23 10:48
I have earlier provided simple demonstrations of CONTEXT and CTXCAT Indexes.

A CTXRULE Index can be used to build a Document Classification application.  This involves indexing a table of "queries" that define the classification.  Queries use the MATCHES clause.
(Note : Like the CONTEXT Index, a call to SYNC_INDEX is required before the rows are indexed).

SQL> create table common_query_classes
2 (classification varchar2(64),
3 query_text varchar2(4000));

Table created.

SQL> create index query_class_index
2 on common_query_classes (query_text)
3 indextype is ctxsys.ctxrule
4 /

Index created.

SQL>
SQL> insert into common_query_classes
2 values ('Players','Gavaskar OR Tendulkar OR Chappell OR Imran OR Botham');

1 row created.

SQL> insert into common_query_classes
2 values ('Grounds','Brabourne OR Wankhede OR Lords');

1 row created.

SQL> commit;

Commit complete.

SQL>


Note that the query predicates are divided by the OR.  They are NOT listed in Alphabetical order.

Now, I test a few queries :

SQL> exec ctx_ddl.sync_index('QUERY_CLASS_INDEX');

PL/SQL procedure successfully completed.

SQL>
SQL> select classification
2 from common_query_classes
3 where MATCHES (query_text,'Tendulkar is a Player at Brabourne') > 0
4 /

CLASSIFICATION
----------------------------------------------------------------
Grounds
Players

SQL>
SQL> select classification
2 from common_query_classes
3 where MATCHES (query_text,'Botham') > 0
4 /

CLASSIFICATION
----------------------------------------------------------------
Players

SQL> select classification
2 from common_query_classes
3 where MATCHES (query_text, 'Kohli is a Player at Wankhede') > 0
4 /

CLASSIFICATION
----------------------------------------------------------------
Grounds

SQL>


Note that, since Kohli is not in the Players list, the last query doesn't return the Classification "Players".
.
.
.

Categories: DBA Blogs

Domain Indexes -- 3 : CTXCAT Index

Sat, 2018-04-21 11:14
In previous posts in December 2017, I had demonstrated a CONTEXT Index.

A CONTEXT Index is used for full-text retrieval from large pieces of text (or document formats stored in LOBs)

A CTXCAT Index is best suited for small fragments of text that are to be indexed with other relational data.

Before I begin with the CTXCAT index, in addition to the CTXAPP role (that I had granted during the earlier demonstration), the account also needs the CREATE TRIGGER privilege.

SQL> grant ctxapp to ctxuser;

Grant succeeded.

SQL> grant create trigger to ctxuser;

Grant succeeded.

SQL>


I can now proceed with the CTXUSER demonstration.

SQL> connect ctxuser/ctxuser
Connected.
SQL> create table books
2 (book_id integer primary key,
3 book_title varchar2(250) not null,
4 book_author varchar2(80),
5 book_subject varchar2(25),
6 shelf_id integer)
7 /

Table created.

SQL>
SQL> insert into books values
2 (1,'A Study In Scarlet','Arthur Conan Doyle','Mystery',1);

1 row created.

SQL> insert into books values
2 (2,'The Sign Of Four','Arthur Conan Doyle','Mystery',1);

1 row created.

SQL> insert into books values
2 (3,'Murder On The Orient Express','Agatha Christie','Mystery',1);

1 row created.

SQL> insert into books values
2 (4,'A Brief History of Time','Stephen Hawking','Science - Physics',2);

1 row created.

SQL>
SQL> insert into books values
2 (5,'2001: A Space Odyssey','Arthur C Clarke','Science Fiction',3);

1 row created.

SQL>
SQL> commit;

Commit complete.

SQL>


Next, I specify what is called an Index Set -- which specifies the structured columns that are to be included in the CTXCAT Index.  I then define the CTXCAT Index on the BOOK_TITLE column.

SQL> begin
2 ctx_ddl.create_index_set('books_set');
3 ctx_ddl.add_index('books_set','book_subject');
4 ctx_ddl.add_index('books_set','shelf_id');
5 end;
6 /

PL/SQL procedure successfully completed.

SQL>
SQL> create index books_title_index
2 on books (book_title)
3 indextype is ctxsys.ctxcat
4 parameters ('index set books_set')
5 /

Index created.

SQL>


Now, I can use the Index to query the table, using the CATSEARCH clause instead of the CONTAINS clause. My query includes both BOOK_TITLE and SHELF_ID

SQL> select book_title,book_author,book_subject,shelf_id
2 from books
3 where catsearch (book_title,'History','shelf_id=1') > 0
4 /

no rows selected

SQL> select book_title,book_author,book_subject,shelf_id
2 from books
3 where catsearch (book_title,'History','shelf_id>1') > 0
4 /

BOOK_TITLE
--------------------------------------------------------------------------------
BOOK_AUTHOR
--------------------------------------------------------------------------------
BOOK_SUBJECT SHELF_ID
------------------------- ----------
A Brief History of Time
Stephen Hawking
Science - Physics 2


SQL>


The CTXCAT Index that I built on BOOK_TITLE also includes BOOK_SUBJECT and SHELF_ID as indexed columns by virtue of the INDEX_SET called "BOOKS_SET".

Now, I add another row and verify if I need to Sync the index (as I had to do with the CONTEXT Index earlier).

SQL> insert into books
2 values
3 (6,'The Selfish Gene','Richard Dawkins','Evolution',2);

1 row created.

SQL> commit;
SQL> select book_title,book_author,book_subject,shelf_id
2 from books
3 where catsearch (book_title,'Gene','book_subject > ''S'' ') > 0
4 /

no rows selected

SQL> select book_title,book_author,book_subject,shelf_id
2 from books
3 where catsearch (book_title,'Gene','book_subject > ''E'' ') > 0
4 /

BOOK_TITLE
--------------------------------------------------------------------------------
BOOK_AUTHOR
--------------------------------------------------------------------------------
BOOK_SUBJECT SHELF_ID
------------------------- ----------
The Selfish Gene
Richard Dawkins
Evolution 2


SQL>


Note, specifically, how I could use the BOOK_SUBJECT in the query as if looking up a separate index on BOOK_SUBJECT.
The new book was included in the index without a call to CTX_DDL.SYNC_INDEX as would be required for the CONTEXT IndexType.

The portion of the query that is on the BOOK_TITLE column does a Text search on this column but the portions on BOOK_SUBJECT an SHELF_ID behave as with regular indexes.


(I know  that some readers will dispute the subject categorization "Evolution"  but I deliberately threw that in so that I  could show a query that uses a predicate filter not on "Science").

.
.
.




Categories: DBA Blogs

The Blog Post that has had more than 55K PageViews

Mon, 2018-03-26 00:25
This Blog Post from 2009 has had more than 55thousand PageViews to date.

Bringing ONLINE a Datafile that is in RECOVER mode because it was OFFLINE

.
.
.
  
Categories: DBA Blogs

An "Awesome" List of Resources

Fri, 2018-03-23 03:13
Here's an "Awesome" List of Resources 

https://github.com/sindresorhus/awesome

.
.
.
 
Categories: DBA Blogs

50K views on my YouTube Channel

Wed, 2018-01-10 01:06
My YouTube channel on Oracle has now exceeded 50thousand views.  A few more subscribers and the subscriber count will exceed 500.

Thank you all !

I have been busy for the past few months but, hopefully, in 2018 will keep adding to my YouTube and BlogSpot content.

.
.
.
 
Categories: DBA Blogs

Domain Indexes -- 2 : STOPLIST for a Context Index

Tue, 2017-12-26 09:29
In the previous example, I created a simple CONTEXTIndex.
For a CONTEXT Index, Oracle automatically applies a default "STOPLIST".  This is a list of common words that are automatically excluded from the Index.
If you notice in the previous post, the words "this", "is", "a", "to", "be" are all in the MY_TEXT column of the MY_TEXT_TABLE but do not appear in the list of tokens in DR$MY_TEXT_INDEX$I.


Can we expand and build our own STOPLIST ?

I start with a new table containing the same rows :

SQL> connect ctxuser/ctxuser
Connected.
SQL> create table another_text_table
2 as select id_column as id_column,
3 my_text as another_text
4 from my_text_table;

Table created.

SQL> select another_text from another_text_table;

ANOTHER_TEXT
--------------------------------------------------------------------------------
This is a long piece of text written by Hemant
Another long text to be captured by the index

SQL>


I then build a custom STOPLIST (which I name as "another_text") and then create the CONTEXT Index.

SQL> begin
2 ctx_ddl.create_stoplist('another_text');
3 ctx_ddl.add_stopword('another_text','Hemant');
4 ctx_ddl.add_stopword('another_text','long');
5 end;
6 /

PL/SQL procedure successfully completed.

SQL>
SQL> create index another_text_index
2 on another_text_table(another_text)
3 indextype is ctxsys.context
4 parameters('stoplist another_text');

Index created.

SQL>


This time, I don't have to use CTX_DDL.SYNC_INDEX as the Index is created after the table is populated. Nevertheless, if I execute DML (INSERT, UPDATE or DELETE) to change data, I will need a call to CTX_DDL.SYNC_INDEX to update the Index.

Now, I check the Tokens that are created in this index.

SQL> select token_text, token_count
2 from dr$another_text_index$i
3 order by token_text
4 /

TOKEN_TEXT TOKEN_COUNT
---------------------------------------------------------------- -----------
A 1
ANOTHER 1
BE 1
BY 2
CAPTURED 1
INDEX 1
IS 1
OF 1
PIECE 1
TEXT 2
THE 1
THIS 1
TO 1
WRITTEN 1

14 rows selected.

SQL>


The STOPLIST words that I defined ('Hemant' and 'long') are not in the Tokens list.  However, since I defined my own Custom STOPLIST, Oracle's default BASIC_STOPLIST has been overriden.  So, now the Tokens list includes words like "a", "be", "by", "is" etc.

Contrast this with the Tokens list in the CONTEXT index that was created on MY_TEXT_TABLE using the default BASIC_STOPLIST :

SQL> select token_text, token_count
2 from dr$my_text_index$i
3 order by token_text
4 /

TOKEN_TEXT TOKEN_COUNT
---------------------------------------------------------------- -----------
ANOTHER 1
CAPTURED 1
HEMANT 1
INDEX 1
LONG 2
PIECE 1
TEXT 2
WRITTEN 1

8 rows selected.

SQL>


So, if you want to build a Custom STOPLIST, make sure you identify all the "common" words you want excluded.
.
.
.

Categories: DBA Blogs

Domain Indexes -- 1 : CONTEXT Indexes

Sun, 2017-12-10 03:17
A CONTEXT Index is one of a class of Domain Indexes.  It is used to build text-retrieval application.
Instead of a regular B-Tree index that captures the entire text of a VARCHAR2 column as a key, you can build an index that consists of "Tokens", words extracted from a long-ish text in the database column.  Searching the index is based on the CONTAINS operator.

Here is a quick demo in 11.2.0.4 :

First I setup a new user for this demo.  I could have used an existing user but my "HEMANT" user has DBA privileges and I want to demonstrate CONTEXT without such privileges.
Here the key grant is "CTXAPP" that is granted to the user.

SQL> create user ctxuser identified by ctxuser default tablespace users;

User created.

SQL> grant create session, create table to ctxuser;

Grant succeeded.

SQL> grant ctxapp to ctxuser;

Grant succeeded.

SQL> alter user ctxuser quota unlimited on users;

User altered.

SQL>


Next, this user creates the demonstration table and index :

SQL> connect ctxuser/ctxuser
Connected.
SQL> create table my_text_table
2 (id_column number primary key,
3 my_text varchar2(2000));

Table created.

SQL> create index my_text_index
2 on my_text_table(my_text)
3 indextype is ctxsys.context;

Index created.

SQL>
SQL> insert into my_text_table
2 values (1,'This is a long piece of text written by Hemant');

1 row created.

SQL> insert into my_text_table
2 values (2,'Another long text to be captured by the index');

1 row created.

SQL> commit;

Commit complete.

SQL>
SQL> -- this update to the index would be a regular background job
SQL> exec ctx_ddl.sync_index('MY_TEXT_INDEX');

PL/SQL procedure successfully completed.

SQL>


Note the call to CTX_DDL.SYNC_INDEX.  Unlike a regular B-Tree index, the CONTEXT (Domain) Index is, by default, *not* updated in real-time when DML occurs against the base table.  (Hopefully, in a future post, I may demonstrate real-time updates to a CTXCAT index, different from a CONTEXT Index).
Only remember that this CONTEXT index is not automatically updated.  If new rows are added to the table, they are not visible through the index until a SYNC_INDEX operation is performed.  The SYNC_INDEX can be scheduled as a job with another call to DBMS_JOB or DBMS_SCHEDULER or itself as part of the CREATE INDEX statement.

Now, let me demonstrate usage of the Index with the CONTAINS operator.

SQL> select id_column as id,
2 my_text
3 from my_text_table
4 where contains (my_text, 'written by Hemant') > 0
5 /

ID
----------
MY_TEXT
--------------------------------------------------------------------------------
1
This is a long piece of text written by Hemant


SQL> select my_text
2 from my_text_table
3 where contains (my_text, 'Another long') > 0
4 /

MY_TEXT
--------------------------------------------------------------------------------
Another long text to be captured by the index

SQL>


Yes, the CONTAINS operator is a bit awkward.  It will be some time before you (or your developers) get used to the syntax !

Besides CTX_DDL, there are a number of other packages in the prebuilt CTXSYS schema that are available :
 CTX_CLS
 CTX_DDL
 CTX_DOC
 CTX_OUTPUT
 CTX_QUERY
 CTX_REPORT
 CTX_THES
 CTX_ULEXER

Since I have created a separate database user, I can also demonstrate the additional objects that are created when the INDEXTYPE IS CTXSYS.CONTEXT.

SQL> select object_type, object_name, to_char(created,'DD-MON-RR HH24:MI') Crtd
2 from user_objects
3 order by object_type, object_name
4 /

OBJECT_TYPE OBJECT_NAME CRTD
------------------- ------------------------------ ------------------------
INDEX DR$MY_TEXT_INDEX$X 10-DEC-17 16:48
INDEX DRC$MY_TEXT_INDEX$R 10-DEC-17 16:48
INDEX MY_TEXT_INDEX 10-DEC-17 16:48
INDEX SYS_C0017472 10-DEC-17 16:48
INDEX SYS_IL0000045133C00006$$ 10-DEC-17 16:48
INDEX SYS_IL0000045138C00002$$ 10-DEC-17 16:48
INDEX SYS_IOT_TOP_45136 10-DEC-17 16:48
INDEX SYS_IOT_TOP_45142 10-DEC-17 16:48
LOB SYS_LOB0000045133C00006$$ 10-DEC-17 16:48
LOB SYS_LOB0000045138C00002$$ 10-DEC-17 16:48
TABLE DR$MY_TEXT_INDEX$I 10-DEC-17 16:48
TABLE DR$MY_TEXT_INDEX$K 10-DEC-17 16:48
TABLE DR$MY_TEXT_INDEX$N 10-DEC-17 16:48
TABLE DR$MY_TEXT_INDEX$R 10-DEC-17 16:48
TABLE MY_TEXT_TABLE 10-DEC-17 16:48

15 rows selected.

SQL>
SQL> select table_name, constraint_name, index_name
2 from user_constraints
3 where constraint_type = 'P'
4 order by table_name, constraint_name
5 /

TABLE_NAME CONSTRAINT_NAME
------------------------------ ------------------------------
INDEX_NAME
------------------------------
DR$MY_TEXT_INDEX$K SYS_IOT_TOP_45136
SYS_IOT_TOP_45136

DR$MY_TEXT_INDEX$N SYS_IOT_TOP_45142
SYS_IOT_TOP_45142

DR$MY_TEXT_INDEX$R DRC$MY_TEXT_INDEX$R
DRC$MY_TEXT_INDEX$R

MY_TEXT_TABLE SYS_C0017472
SYS_C0017472


SQL>


Yes, that is a large number of database objects besides MY_TEXT_TABLE and MY_TEXT_INDEX.  SYS_C0017472 is, of course, the Primary Key Index on MY_TEXT_TABLE (on the ID_COLUMN column).  The others are interesting.

The "Tokens" I mentioned in the first paragraph are, for the purpose of this table MY_TEXT_TABLE, in the DR$MY_TEXT_INDEX$I.

SQL> desc DR$MY_TEXT_INDEX$I
Name Null? Type
----------------------------------------- -------- ----------------------------
TOKEN_TEXT NOT NULL VARCHAR2(64)
TOKEN_TYPE NOT NULL NUMBER(10)
TOKEN_FIRST NOT NULL NUMBER(10)
TOKEN_LAST NOT NULL NUMBER(10)
TOKEN_COUNT NOT NULL NUMBER(10)
TOKEN_INFO BLOB

SQL> select token_text, token_count
2 from dr$my_text_index$i
3 /

TOKEN_TEXT TOKEN_COUNT
---------------------------------------------------------------- -----------
ANOTHER 1
CAPTURED 1
HEMANT 1
INDEX 1
LONG 2
PIECE 1
TEXT 2
WRITTEN 1

8 rows selected.

SQL>


I have been busy in the last few months and have not published much this quarter. Hopefully, I will get more time in the coming weeks to explore CONTEXT, CTXCAT and Domain Indexes.
.
.
.

Categories: DBA Blogs

Creating a PDB in a desired location in 12.2

Wed, 2017-11-15 08:53
A video on creating a Pluggable Database in a desired location, using the command-line.
.
.
.

Categories: DBA Blogs

UNIQUE LOCAL (Partitioned) Index

Mon, 2017-11-06 21:44
It is easy to create a default GLOBAL Index that is defined as a Unique Index on a column (or composite of columns) containing unique values.

But what if you have a Partitioned Table and want to create a LOCAL (i.e. equi-partitioned with the table) Index as a Unique Index ?  Are there any constraints ?

Let me demonstrate a Partitioned table listing Users by Region Code.

SQL> create table users
2 (region_code varchar2(3),
3 username varchar2(30),
4 account_status varchar2(32),
5 created date,
6 profile varchar2(128))
7 partition by range (region_code)
8 (partition a_m values less than ('N'),
9 partition n_r values less than ('S'),
10 partition s_z values less than (MAXVALUE))
11 /

Table created.

SQL>
SQL> insert into users
2 select substr(username,1,3), username, account_status, created, profile
3 from dba_users
4 /

39 rows created.

SQL>
SQL> commit;

Commit complete.

SQL> exec dbms_stats.gather_table_stats('','USERS');

PL/SQL procedure successfully completed.

SQL>
SQL> col partition_name format a30
SQL> select partition_name, num_rows
2 from user_tab_partitions
3 where table_name = 'USERS'
4 order by partition_position
5 /

PARTITION_NAME NUM_ROWS
------------------------------ ----------
A_M 18
N_R 9
S_Z 12

SQL>


I now verify that I can create a GLOBAL (non-partitioned) Unique Index on USERNAME.

SQL> create unique index users_username_u1 on users(username) global;

Index created.

SQL> drop index users_username_u1;

Index dropped.

SQL> create unique index users_username_u1 on users(username);

Index created.

SQL> drop index users_username_u1;

Index dropped.

SQL>


I now verify that I can create a Non-Unique LOCAL Index (being equi-partitioned, the index is partitioned by REGION_CODE).  (Being equi-partitioned, the default behaviour is the Index Partition Names inherit from the Table Partition Names).

SQL> create index users_username_l1 on users(username) local;

Index created.

SQL> select partition_name, num_rows
2 from user_ind_partitions
3 where index_name = 'USERS_USERNAME_L1'
4 order by partition_position
5 /

PARTITION_NAME NUM_ROWS
------------------------------ ----------
A_M 18
N_R 9
S_Z 12

SQL>
SQL> drop index users_username_l1;

Index dropped.

SQL>


I've proven (with the GLOBAL Index) that USERNAME is Unique across the whole table.  Can I create a Unique LOCAL Index on this column ?

SQL> create unique index users_username_u_l1 on users(username) local;
create unique index users_username_u_l1 on users(username) local
*
ERROR at line 1:
ORA-14039: partitioning columns must form a subset of key columns of a UNIQUE index


SQL>


Note the Error Message. The Partition Key must be a subset of the Unique Index columns.  Let me try adding the Partition Key  (in my example table, the Partition Key is a single column -- it could be a composite of multiple columns.  In that case all the columns of the Partition Key must for a subset of the Unique Index).

SQL> create unique index users_rc_un_u_l1 on users(region_code, username) local;

Index created.

SQL> select partition_name, num_rows
2 from user_ind_partitions
3 where index_name = 'USERS_RC_UN_U_L1'
4 order by partition_position
5 /

PARTITION_NAME NUM_ROWS
------------------------------ ----------
A_M 18
N_R 9
S_Z 12

SQL> drop index users_rc_un_u_l1;

Index dropped.

SQL> create unique index users_un_rc_u_l1 on users(username, region_code) local;

Index created.

SQL> select partition_name, num_rows
2 from user_ind_partitions
3 where index_name = 'USERS_UN_RC_U_L1'
4 order by partition_position
5 /

PARTITION_NAME NUM_ROWS
------------------------------ ----------
A_M 18
N_R 9
S_Z 12

SQL>

It doesn't matter if I put the Partition Key (REGION_CODE) before or after the target column(s) (USERNAME) when I create the LOCAL Unique Index.  What is necessary is that the Partition Key be a subset of the Unique Index definition.
.
.
.

Categories: DBA Blogs

Partitioned Indexes

Fri, 2017-09-29 10:15
Most discussions about Partitioning in Oracle are around Table Partitioning.  Rarely do we come across Index Partitioning.
A couple of days ago, there was an Oracle Community question on Partitioned Indexes.

So, here is a quick listing of Index Partitioning options  (these tests are in 11.2.0.4)


First, I start with a regular, non-partitioned table.

SQL> create table non_partitioned  
2 (id_col number,
3 data_col_1 number,
4 data_col_2 number,
5 data_col_3 varchar2(15)
6 )
7 /

Table created.

SQL>


I now attempt to create an Equi-Partitioned (LOCAL) Index on it.

SQL> create index equi_part on non_partitioned (id_col) local;
create index equi_part on non_partitioned (id_col) local
*
ERROR at line 1:
ORA-14016: underlying table of a LOCAL partitioned index must be partitioned


SQL>


As expected I can't create a LOCAL index on a non-partitioned table.

Can I create any partitioned index on this table ?

I try two different GLOBAL PARTITIONed Indexes

SQL> create index global_part   
2 on non_partitioned (id_col) global
3 partition by range (id_col)
4 (partition p_100 values less than (101),
5 partition p_200 values less than (201)
6 )
7 /
)
*
ERROR at line 6:
ORA-14021: MAXVALUE must be specified for all columns


SQL>
SQL> create index global_part
2 on non_partitioned (id_col) global
3 partition by range (id_col)
4 (partition p_100 values less than (101),
5 partition p_200 values less than (201),
6 partition p_max values less than (MAXVALUE)
7 )
8 /

Index created.

SQL>
SQL> create index global_part_comp
2 on non_partitioned (id_col, data_col_3) global
3 partition by range (id_col, data_col_3)
4 (partition p_1 values less than (101,'M'),
5 partition p_2 values less than (101,MAXVALUE),
6 partition p_3 values less than (201,'M'),
7 partition p_4 values less than (201,MAXVALUE),
8 partition p_max values less than (MAXVALUE, MAXVALUE)
9 )
10 /

Index created.

SQL>


So, I must have a MAXVALUE partition for the Index.  Note that the two indexes above are now Partitioned without the table itself being partitioned.

SQL> select index_name, partitioned
2 from user_indexes
3 where table_name = 'NON_PARTITIONED'
4 order by 1
5 /

INDEX_NAME PAR
------------------------------ ---
GLOBAL_PART YES
GLOBAL_PART_COMP YES

SQL>


The above indexes are Prefixed Global Partitioned Indexes. Can I create a Non-Prefixed Global Partitioned Index -- an Index where the Partition Key is not formed by the left-most columns of the index.

SQL> create index global_part_nonprefix
2 on non_partitioned (id_col, data_col_3) global
3 partition by range (data_col_1)
4 (partition p_1 values less than (101),
5 partition p_2 values less than (201),
6 partition p_max values less than (MAXVALUE)
7 )
8 /
partition by range (data_col_1)
*
ERROR at line 3:
ORA-14038: GLOBAL partitioned index must be prefixed


SQL>
SQL> !oerr ora 14038
14038, 00000, "GLOBAL partitioned index must be prefixed"
// *Cause: User attempted to create a GLOBAL non-prefixed partitioned index
// which is illegal
// *Action: If the user, indeed, desired to create a non-prefixed
// index, it must be created as LOCAL; otherwise, correct the list
// of key and/or partitioning columns to ensure that the index is
// prefixed

SQL>


So, I have proved that a Non-Partitioned Table cannot have a LOCAL Partitioned Index or a Non-Prefixed Global Partitioned Index but can still have a Global Partitioned Index where the Partition Key is left-prefixed from the Index Key. Also, that a Global Partitioned Index can be a Composite Index with columns of different datatypes.

Let me now proceed with a Partitioned Table.

SQL> create table partitioned
2 (id_col number,
3 data_col_1 number,
4 data_col_2 number,
5 data_col_3 varchar2(15)
6 )
7 partition by range (id_col)
8 (partition p_100 values less than (101),
9 partition p_200 values less than (201),
10 partition p_max values less than (MAXVALUE)
11 )
12 /

Table created.

SQL>


First, the Equi-Partitioned (LOCAL) Index.

SQL> create index part_equi_part
2 on partitioned (id_col) local
3 /

Index created.

SQL> select partition_name, partition_position
2 from user_ind_partitions
3 where index_name = 'PART_EQUI_PART'
4 order by 2
5 /

PARTITION_NAME PARTITION_POSITION
------------------------------ ------------------
P_100 1
P_200 2
P_MAX 3

SQL>


The usage of the LOCAL keyword instead of GLOBAL defines the Index as equi-partitioned with the table.  Index Partitions are automatically created to match the Table Partitions with the same Partition Names.  It is possible to create a LOCAL Partitioned Index and manually specify Partition Names but this, in my opinion, is a bad idea.  Attempting to manually name each Partition for the Index can result in a mis-match between Table Partition Names and Index Partition Names.

Next, I define two GLOBAL Partitioned Indexes on this table.

SQL> create index part_gbl_part  
2 on partitioned (data_col_1) global
3 partition by range (data_col_1)
4 (partition p_1 values less than (1001),
5 partition p_2 values less than (2001),
6 partition p_3 values less than (3001),
7 partition p_4 values less than (4001),
8 partition p_max values less than (MAXVALUE)
9 )
10 /

Index created.

SQL> create index part_gbl_part_comp
2 on partitioned (data_col_2, data_col_3) global
3 partition by range (data_col_2, data_col_3)
4 (partition p_a values less than (10, 'M'),
5 partition p_b values less than (10, MAXVALUE),
6 partition p_c values less than (20, 'M'),
7 partition p_d values less than (20, MAXVALUE),
8 partition p_e values less than (30, 'M'),
9 partition p_f values less than (30, MAXVALUE),
10 partition p_max values less than (MAXVALUE, MAXVALUE)
11 )
12 /

Index created.

SQL>
SQL> l
1 select index_name, partition_name, partition_position
2 from user_ind_partitions
3 where index_name in
4 (select index_name from user_indexes
5 where table_name = 'PARTITIONED'
6 )
7* order by 1,3
SQL> /

INDEX_NAME PARTITIO PARTITION_POSITION
------------------ -------- ------------------
PART_EQUI_PART P_100 1
PART_EQUI_PART P_200 2
PART_EQUI_PART P_MAX 3
PART_GBL_PART      P_1                       1
PART_GBL_PART P_2 2
PART_GBL_PART P_3 3
PART_GBL_PART P_4 4
PART_GBL_PART P_MAX 5
PART_GBL_PART_COMP P_A                       1
PART_GBL_PART_COMP P_B 2
PART_GBL_PART_COMP P_C 3
PART_GBL_PART_COMP P_D 4
PART_GBL_PART_COMP P_E 5
PART_GBL_PART_COMP P_F 6
PART_GBL_PART_COMP P_MAX 7

15 rows selected.

SQL>


The Equi-Partitioned (LOCAL) Index has the same number (and, recommended, names) of Partitions as the Table.
However, the GLOBAL Indexes can have different numbers of Partitions.

As with the first case, I cannot create a Global Non-Prefixed Partitioned Index (where the Index Partition key is not  a left-prefix of the Index).

SQL> create index part_global_part_nonprefix
2 on partitioned (id_col, data_col_3) global
3 partition by range (data_col_1)
4 (partition p_1 values less than (101),
5 partition p_2 values less than (201),
6 partition p_max values less than (MAXVALUE)
7 )
8 /
partition by range (data_col_1)
*
ERROR at line 3:
ORA-14038: GLOBAL partitioned index must be prefixed


SQL>


In this blog post, I haven't touched on Partial Indexing (a 12c feature).

I haven't touched on Unique LOCALly Partitioned Indexes.

I haven't demonstrated the impact of Partition Maintenance operations (TRUNCATE, DROP, MERGE, ADD, SPLIT) on LOCAL and GLOBAL Indexes here -- although I have touched on such operations and LOCAL indexes in earlier blog posts.
.
.
.

Categories: DBA Blogs

Pages