Composite Index Column Order: Why It Matters
A composite index on (a, b) is not interchangeable with one on (b, a) — column order determines exactly which queries the index can actually serve. Here's the leftmost-prefix rule and the two heuristics for getting the order right.
The leftmost-prefix rule
A composite (multi-column) index is only efficiently usable starting from its leftmost column. An index on (department_id, hire_date) can serve a query filtering on department_id alone, or on both columns together — but a query filtering on hire_date alone generally can't use it efficiently, because the index isn't independently sorted by hire_date.
This single rule is why column order in a composite index is a real design decision, not a cosmetic one — the same two columns in a different order serve a different set of queries.
The same columns, two different indexes
Neither index is "wrong" — they serve different query patterns.
CREATE INDEX idx_a ON employees (department_id, hire_date);
-- Serves: WHERE department_id = ?
-- Serves: WHERE department_id = ? AND hire_date > ?
-- Does NOT efficiently serve: WHERE hire_date > ? (alone)
CREATE INDEX idx_b ON employees (hire_date, department_id);
-- Serves: WHERE hire_date > ?
-- Serves: WHERE hire_date > ? AND department_id = ?
-- Does NOT efficiently serve: WHERE department_id = ? (alone)
Two rules of thumb for ordering columns
| Rule | Why |
|---|---|
| Equality columns before range columns | After a range condition (>, <, BETWEEN), the index can't use later columns to narrow the search further — put columns compared with = first, ranged columns last |
| More selective / more frequently filtered-alone columns first | The leading column determines which single-column queries the index can serve at all — put the column most often queried by itself, or that narrows the result the most, in that position |
A bonus: matching ORDER BY
If a query filters on the leading column(s) with equality and then sorts by the next column, the same composite index can often satisfy the sort too — avoiding a separate sort step in the execution plan. WHERE department_id = 60 ORDER BY hire_date can be served entirely by the (department_id, hire_date) index above: Oracle finds the matching rows and they're already in hire_date order within that department.