Oracle Function-Based Index: Syntax, Example & Gotchas
A plain index on a column can't help a query that filters on a function of that column — UPPER(last_name), TRUNC(hire_date), and similar expressions all need their own index. Here's how function-based indexes work, and the exact-match rule that decides whether the optimizer actually uses one.
Indexing the result of an expression, not just a column
A function-based index indexes the result of a function or expression applied to a column, rather than the raw column value. It exists to fix a specific, common problem: a plain index on last_name can't be used by a query filtering on UPPER(last_name), because the index stores raw values and the query is asking about a transformed one.
The problem, and the fix
A case-insensitive search is the classic use case.
-- A plain index on last_name does NOT help this query
SELECT * FROM employees WHERE UPPER(last_name) = 'SMITH';
-- Fix: index the exact expression the query uses
CREATE INDEX idx_emp_upper_lastname ON employees (UPPER(last_name));
-- Now this uses the function-based index:
SELECT * FROM employees WHERE UPPER(last_name) = 'SMITH';
The expression has to match exactly
This is the rule that trips people up: the optimizer only uses a function-based index when the query's WHERE clause contains the exact same expression the index was built on — not just a semantically similar one. An index on UPPER(last_name) won't be used by a query filtering on LOWER(last_name), or on plain last_name with no function at all. Consistency between how the index is built and how queries are written is what makes function-based indexes work.
Alternative: a virtual column
Oracle 11g+ lets you name the expression as a column and index that instead — sometimes clearer for reporting and reuse.
ALTER TABLE employees ADD (
last_name_upper VARCHAR2(50) GENERATED ALWAYS AS (UPPER(last_name)) VIRTUAL
);
CREATE INDEX idx_emp_lnu ON employees (last_name_upper);
-- Now this uses the index too, and the column is queryable by name:
SELECT * FROM employees WHERE last_name_upper = 'SMITH';
Beyond case-insensitive search
Function-based indexes aren't just for UPPER/LOWER. Common uses include indexing TRUNC(hire_date) to speed up date-only comparisons that ignore the time component, indexing an arithmetic expression used consistently in reports, or indexing a substring extraction that's queried often. The rule is the same in every case: the index only helps queries that use the identical expression.