Identify Indexed Columns in Oracle Database Tables
Identify Indexed Columns in Oracle Database Tables
Purpose
Which columns does this index cover, and in what order? A developer asks it before adding an index that might already exist. A DBA asks it when a query scans a whole table and the team swears an index is in place. The answer is in the data dictionary, one row per indexed column.
DBA_IND_COLUMNS describes the columns of all the indexes on all tables and clusters in the database. The query below takes an index name and lists its columns in the order they sit in the index. Two further queries start from a table name instead, and one folds each index into a single line with a column list.
The order matters. A composite index serves queries that use its leading columns, so (customer_id, order_date) and (order_date, customer_id) are two different indexes with different uses.
Code
1set verify off
2col index_owner format a20
3col index_name format a30
4col column_name format a30
5select index_owner
6, index_name
7, column_position
8, column_name
9, descend
10from dba_ind_columns
11where index_name = upper('&index')
12order by index_owner
13, index_name
14, column_position
15/
To start from a table, list the index columns for every index on it:
1col table_name format a30
2select index_name
3, column_position
4, column_name
5, descend
6from dba_ind_columns
7where table_owner = upper('&owner')
8and table_name = upper('&table')
9order by index_name
10, column_position
11/
To see each index as one line, with its type and state:
1col columns format a60
2select i.index_name
3, i.uniqueness
4, i.visibility
5, i.status
6, listagg(c.column_name, ', ') within group (order by c.column_position) columns
7from dba_indexes i
8, dba_ind_columns c
9where c.index_owner = i.owner
10and c.index_name = i.index_name
11and i.table_owner = upper('&owner')
12and i.table_name = upper('&table')
13group by i.index_name
14, i.uniqueness
15, i.visibility
16, i.status
17order by i.index_name
18/
To read the expressions behind a function-based index:
1select index_name
2, column_position
3, column_expression
4from dba_ind_expressions
5where table_owner = upper('&owner')
6and table_name = upper('&table')
7order by index_name
8, column_position
9/
Code Breakdown
The first query
dba_ind_columnsholds one row per column of each index. Its columns are the same asALL_IND_COLUMNS.index_owneris in the select list on purpose. Two schemas can own indexes with the same name, and a query onindex_namealone returns the columns of both mixed together. The owner column and theORDER BYkeep them apart.column_positionis the position of the column within the index. Position 1 is the leading column.column_nameis the name of the column, or the attribute of an object type column.descendshowsASCorDESCfor the sort direction of that column in the index.where index_name = upper('&index')filters on the name you type. SQL*Plus replaces&indexwith the value you enter when it runs the statement. The quotes are already in the query, so enter the name without quotes. If the index was created with a mixed-case name, enter it exactly asDBA_INDEXES.INDEX_NAMEshows it and remove theuppercall.set verify offstops SQL*Plus from printing the statement before and after it replaces the substitution variable.
The second query
table_ownerandtable_namefind all the indexes on one table. Always give the owner, because the same table name can exist in more than one schema.- The
ORDER BYputs the columns of each index together and in position order, so each index reads as a block.
The third query
dba_indexeshas one row per index, withUNIQUENESS(UNIQUEorNONUNIQUE),VISIBILITY(VISIBLEorINVISIBLEto the optimizer) andSTATUS.listagg(... ) within group (order by c.column_position)joins the column names of one index into one string, in index order. TheGROUP BYlists every non-aggregated column of the select.- The join links
dba_indexes.ownertodba_ind_columns.index_owner, together with the index name, so an index in one schema never picks up columns from another.
The fourth query
dba_ind_expressionslists the expressions of function-based indexes.column_expressionholds the expression andcolumn_positionsays where it sits in the index.
Key Points
- Scope of the views.
DBA_IND_COLUMNScovers the whole database.ALL_IND_COLUMNScovers indexes on tables the current user can access.USER_IND_COLUMNScovers indexes the current user owns and indexes on the tables the current user owns, and it has noINDEX_OWNERorTABLE_OWNERcolumns. UseUSER_IND_COLUMNSwhen you lack access to theDBA_view. - Position is the order.
COLUMN_POSITIONis the position of the column or attribute within the index. Sort by it, or the column list has no meaning. - Function-based indexes show an expression, not a plain column. The expression is in
DBA_IND_EXPRESSIONS. A column list for such an index is incomplete without that view. - Index type tells you more.
DBA_INDEXES.INDEX_TYPEshowsNORMAL,BITMAP,FUNCTION-BASED NORMAL,IOT - TOP,DOMAINand other values. A bitmap index and a B-tree index on the same column are different tools. - Status. For a nonpartitioned index,
STATUSisVALIDorUNUSABLE. An unusable index still shows its columns here, so readSTATUSbefore you trust the column list. For a partitioned index, look at the partition views such asDBA_IND_PARTITIONS. - Visibility. An
INVISIBLEindex is hidden from the optimizer. It still shows in these views, so a column list alone does not prove that queries can use the index. - Reference columns. For an index on a user-defined
REFcolumn, the names shown are the attribute names with theREFcolumn name as a prefix.
Insights and Best Practices
Check before you create
Before adding an index, run the second query for the table. If an index already starts with the columns you plan to index, a new one adds write cost with no read gain. Oracle says that when rows are inserted or deleted, all indexes on the table must be updated, and that when a column is updated, all indexes that contain the column must be updated. An index whose columns are a leading subset of another index is a candidate for removal, once you have checked that nothing depends on it.
Read the leading column
Take an index on (first_name, last_name) and a query with WHERE last_name = 'KING'. The leading column is not in the predicate, so the optimizer may not choose the index. It may choose an index skip scan instead. The order of the columns decides which queries the index serves, which is why you look at column_position and not at the list of names alone.
Compare queries to indexes
Take the WHERE clause of a slow statement and put the columns it filters on next to the index columns. If the filter columns are the second and third columns of an index, and the first column is not in the predicate, the statement has no good index. The fix may be a new index, a different column order, or a change to the statement.
Watch for expressions
If a WHERE clause wraps a column in a function, such as UPPER(last_name), Oracle lists "the query predicate applies a function to the indexed column" as a reason for the optimizer to consider a full table scan. Unless the index is a function-based index, the database indexes the values of the column, not the values with the function applied. A function-based index on the expression is the exception. The fourth query shows the expression behind a function-based index.
Document with the one-line form
The third query gives a compact index inventory per table. Spool it for the key tables each quarter and keep the files. When a column vanishes from an index list, you will know when the change came.
When to Run This
- Before creating an index, to see if one with the same leading columns exists
- When a query does a full table scan and an index is expected
- During a schema review, to find indexes with the same column list
- Before dropping an index, to see what it covers
- After a migration, to compare the indexes on the old and new schema
- When tuning a join, to confirm that the join columns are indexed in the right order
Troubleshooting Common Issues
If the first query returns no rows, check the spelling and the case of the index name, and check the owner. Query DBA_INDEXES with a LIKE on the name to find the exact spelling. If you query USER_IND_COLUMNS or ALL_IND_COLUMNS, the index may be outside what your account can see.
If the query fails with ORA-00942, the message is "table or view does not exist". Oracle lists two causes: the object does not exist, or the account does not have access to it. When the DBA_ view is the object, the account most likely lacks access. Use ALL_IND_COLUMNS or USER_IND_COLUMNS instead, or ask for access.
If SQL*Plus does not prompt for the value, SET DEFINE may be off. SET DEFINE ON turns the scan for substitution variables back on, and the default prefix character is &.
If a column_name does not match any column of the table, run the fourth query. The index may be function-based, and the expression is listed there.
If an index appears in the list but queries do not use it, check VISIBILITY and STATUS in the third query. An invisible index is hidden from the optimizer. Oracle also says that an unusable index is ignored by the optimizer, so rebuild it or drop and re-create it before you expect queries to use it.
References
- DBA_IND_COLUMNS (Oracle Database 19c Reference) - Scope of the view and its link to the
ALL_column list. - ALL_IND_COLUMNS (Oracle Database 19c Reference) - Column list including
COLUMN_POSITIONandDESCEND, and the scope of theUSER_view. - ALL_INDEXES (Oracle Database 19c Reference) -
INDEX_TYPE,UNIQUENESS,STATUSandVISIBILITYvalues used in the third query. - ALL_IND_EXPRESSIONS (Oracle Database 19c Reference) - Expressions of function-based indexes and their position in the index.
- DEFINE (SQL*Plus 19c User's Guide) - How SQL*Plus substitutes
&and&&variables. - SET System Variable Summary (SQL*Plus 19c User's Guide) - The
SET DEFINEandSET VERIFYsettings used in this post. - ORA-00942 (Oracle Database Error Help) - The message text and the causes Oracle lists, including missing access to the table or view.
- LISTAGG (Oracle Database 19c SQL Language Reference) - The aggregate function that builds the column list.
- Managing Indexes (Oracle Database 19c Administrator's Guide) - Index maintenance cost on insert, delete and update, and the statement that an unusable index is ignored by the optimizer.
- Optimizer Access Paths (Oracle Database 19c SQL Tuning Guide) - Why the optimizer may skip an index when its leading column is not in the predicate, the index skip scan, and why a function on an indexed column prevents index use unless the index is function-based.
- Function-Based Indexes (ORACLE-BASE) - Community walkthrough of indexing an expression so a function in the
WHEREclause can use an index.