Oracle Database
Indexes
To follow along with the examples in this lesson, use the course dataset from Lesson 8.
What Is an Index?
An index is a separate data structure Oracle maintains alongside a table to make lookups faster. Without an index, finding a specific row requires Oracle to read every row in the table from top to bottom — a full table scan. With an index on the searched column, Oracle jumps directly to the matching rows, the same way a book's index lets you find a topic without reading every page.
Indexes come with a cost. Every time a row is inserted, updated, or deleted Oracle must also update every index on that table. On read-heavy tables with occasional writes, indexes are almost always worthwhile. On write-heavy tables with few lookups, too many indexes slow down inserts and updates more than they help queries. Choosing what to index — and what not to — is one of the most important decisions in database design.
- An index speeds up SELECT queries that filter or sort on the indexed column
- Oracle automatically creates an index on every PRIMARY KEY and UNIQUE constraint
- Additional indexes must be created manually with
CREATE INDEX - Indexes consume disk space and slow down INSERT, UPDATE, and DELETE operations
- Oracle's query optimiser decides whether to use an index — creating one does not guarantee it will be used
- Query
USER_INDEXESandUSER_IND_COLUMNSto see existing indexes
Creating and Dropping Indexes
Use CREATE INDEX to add an index to any column or combination of columns. The index name should be descriptive — convention is idx_tablename_columnname. A unique index enforces that no two rows share the same value in the indexed column, in addition to speeding up lookups. Use DROP INDEX to remove an index when it is no longer needed.
-- Create a standard B-tree index on order_date.
-- Queries that filter or sort on order_date will benefit from this index.
CREATE INDEX idx_orders_order_date
ON dataplexa_store.orders (order_date);
-- Create a composite index on two columns.
-- Useful when queries filter on both customer_id AND status together.
CREATE INDEX idx_orders_customer_status
ON dataplexa_store.orders (customer_id, status);
-- Create a unique index -- enforces uniqueness AND speeds up lookups.
CREATE UNIQUE INDEX idx_customers_email
ON dataplexa_store.customers (email);
-- Remove an index that is no longer useful.
DROP INDEX idx_orders_order_date;
-- List all indexes on the orders table.
SELECT index_name, index_type, uniqueness
FROM user_indexes
WHERE table_name = 'ORDERS'
ORDER BY index_name;Index created.
-- CREATE INDEX idx_orders_customer_status
Index created.
-- CREATE UNIQUE INDEX idx_customers_email
Index created.
-- DROP INDEX
Index dropped.
-- USER_INDEXES for ORDERS table
INDEX_NAME | INDEX_TYPE | UNIQUENESS
-----------------------------|------------|----------
IDX_ORDERS_CUSTOMER_STATUS | NORMAL | NONUNIQUE
PK_ORDERS | NORMAL | UNIQUE
(2 rows selected)
PK_ORDERSwas created automatically when the primary key constraint was defined — Oracle always indexes primary keys- The composite index
idx_orders_customer_statusis most useful when a query filters on both columns — queries filtering only onstatusalone cannot use it efficiently - Table names in
USER_INDEXESare stored in uppercase —WHERE table_name = 'ORDERS'must match exactly DROP INDEXdoes not require specifying the table — index names are unique within a schema
B-tree vs Bitmap Indexes
Oracle supports two main index types. A B-tree index is the default. It organises values in a balanced tree structure, making it fast for equality lookups, range queries, and sorting. It works well on columns with many distinct values — customer IDs, order dates, email addresses. A bitmap index stores a compact bit array for each distinct value. It is highly efficient for columns with very few distinct values — status flags, gender, country codes — especially in data warehouse queries that combine multiple such columns. Bitmap indexes are not suitable for tables with frequent inserts and updates because maintaining them under concurrent writes is expensive.
-- B-tree index: best for high-cardinality columns (many distinct values).
-- order_date has thousands of distinct values -- B-tree is correct here.
CREATE INDEX idx_orders_order_date
ON dataplexa_store.orders (order_date);
-- Bitmap index: best for low-cardinality columns (few distinct values).
-- status has only 4 possible values: pending, shipped, delivered, cancelled.
-- Suitable for a reporting/data warehouse environment, not an OLTP system.
CREATE BITMAP INDEX idx_orders_status_bmp
ON dataplexa_store.orders (status);
-- Function-based index: index the result of an expression.
-- Useful when queries filter on UPPER(email) for case-insensitive lookups.
CREATE INDEX idx_customers_email_upper
ON dataplexa_store.customers (UPPER(email));
-- Now this query can use the index instead of scanning the full table:
SELECT customer_id, first_name, last_name
FROM dataplexa_store.customers
WHERE UPPER(email) = UPPER('Sarah.Chen@Email.Com');Index created.
-- CREATE BITMAP INDEX idx_orders_status_bmp
Index created.
-- CREATE INDEX idx_customers_email_upper
Index created.
-- Query using the function-based index
CUSTOMER_ID | FIRST_NAME | LAST_NAME
------------|------------|----------
14 | Sarah | Chen
(1 row selected)
- B-tree indexes are the safe default for most columns — use bitmap only in read-heavy reporting environments
- A function-based index stores the pre-computed result of an expression — without it,
WHERE UPPER(email) = ...would force a full table scan because the index on the raw email column cannot be used - Bitmap indexes should not be used on OLTP tables with concurrent writes — row-level locking conflicts make them impractical
When Oracle Does Not Use an Index
Creating an index does not guarantee Oracle will use it. The query optimiser weighs the cost of using the index against the cost of a full table scan and picks whichever is cheaper. Several common patterns prevent an index from being used even when one exists on the queried column.
-- These patterns suppress index usage on the indexed column.
-- PATTERN 1: wrapping the column in a function
-- The index is on order_date, not on TRUNC(order_date) -- full scan used
SELECT * FROM dataplexa_store.orders WHERE TRUNC(order_date) = DATE '2024-06-01';
-- FIX: use a range instead of wrapping the column
SELECT * FROM dataplexa_store.orders
WHERE order_date >= DATE '2024-06-01'
AND order_date < DATE '2024-06-02';
-- PATTERN 2: implicit type conversion
-- customer_id is NUMBER but the value is passed as a string -- index suppressed
SELECT * FROM dataplexa_store.orders WHERE customer_id = '14';
-- FIX: match the data type exactly
SELECT * FROM dataplexa_store.orders WHERE customer_id = 14;
-- PATTERN 3: leading wildcard in LIKE
-- Oracle cannot use a B-tree index to search for values ending in a pattern
SELECT * FROM dataplexa_store.customers WHERE email LIKE '%@gmail.com';
-- FIX: anchor the wildcard at the end if possible
SELECT * FROM dataplexa_store.customers WHERE email LIKE 'sarah%';-- The fixed versions allow Oracle to use the index on order_date,
-- customer_id, and email respectively.
-- You can verify index usage with EXPLAIN PLAN:
EXPLAIN PLAN FOR
SELECT * FROM dataplexa_store.orders WHERE order_date >= DATE '2024-06-01';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- Look for "INDEX RANGE SCAN" (index used) vs "TABLE ACCESS FULL" (index not used)
- Wrapping a column in a function —
TRUNC(),TO_CHAR(),UPPER()— prevents a standard index from being used unless a function-based index exists for that exact expression - Passing a string where a NUMBER column is expected causes Oracle to implicitly convert every row, suppressing the index
- A leading wildcard
LIKE '%value'cannot use a B-tree index because the index is sorted by the start of the value, not the end EXPLAIN PLANfollowed byDBMS_XPLAN.DISPLAYis the standard Oracle tool for checking whether a query uses an index
Lesson Summary
| Concept | What It Means |
|---|---|
| Full table scan | Oracle reads every row — happens when no usable index exists |
| B-tree index | Default index type — best for high-cardinality columns, equality and range queries |
| Bitmap index | Best for low-cardinality columns in read-heavy environments — not for OLTP |
| Function-based index | Indexes the result of an expression — needed when the column is wrapped in a function in WHERE |
| Composite index | Index on multiple columns — most effective when all leading columns appear in the query filter |
| USER_INDEXES | Data dictionary view listing all indexes in the current schema |
| EXPLAIN PLAN | Shows Oracle's execution plan — reveals whether an index scan or full table scan is used |
Practice Questions
Practice 1. Oracle automatically creates an index when you define which two types of constraints?
Practice 2. A query filters on UPPER(email). There is a standard index on the email column. Will Oracle use it?
Practice 3. When is a bitmap index a better choice than a B-tree index?
Practice 4. You have an index on order_date but a query using WHERE TRUNC(order_date) = DATE '2024-06-01' is running slowly. How do you fix it?
Practice 5. What Oracle tool shows whether a query is using an index or performing a full table scan?
Quiz
Quiz 1. You create an index on the status column, which has only four distinct values. Which index type is most appropriate for a data warehouse environment?
Quiz 2. A composite index exists on (customer_id, status). A query filters only on status. How effectively can Oracle use this index?
Quiz 3. Which of these WHERE clauses allows Oracle to use a standard B-tree index on order_date?
Quiz 4. What is the main downside of adding many indexes to a frequently updated table?
Quiz 5. You want a case-insensitive email lookup using WHERE UPPER(email) = UPPER(:input) to use an index. What must you create?
Next up — Introduction to PL/SQL — What PL/SQL is, how a PL/SQL block is structured, and how to declare variables and write your first procedural logic in Oracle.