Hi.
I am forced to use multiple UNION ALL just because ORACLE will disregard all my Indexes if I use CASE statement in WHERE clause.
This is a huge inconvenience for me because I have a large query and a lot of CASE(s), so I have to repeat the same query many times.
And result is dramatic:
with UNION AL - query runs 1 SEC.
with CASE statements in WHERE clause - it runs 30 MIN.
So, the question is - WHY???
I used to work with other databases, and coming to ORACLE makes me feel as on a large minefield, when you always afraid to do something that will cause ORACLE to stop using indexes.
Applying any function to a column will "break" the indexes in Oracle. So you'll get a full table scan. AFAIK other RDBMSes behave in a similar way.
For example, the following three queries all return the same row. But only the first one uses the index. Because it's the only one without a function applied to the column:
create table t (
x int not null,
y varchar2(10) not null
);
insert into t
select rownum, 'XXXXXXXX' from dual connect by level <= 1000;
commit;
create index i on t (x);
exec dbms_stats.gather_table_stats(user, 't');
select /*+ gather_plan_statistics */* from t
where x = 2;
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t where x = 2
Plan hash value: 1667465620
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| T |
| 2 | INDEX RANGE SCAN | I |
--------------------------------------------
select /*+ gather_plan_statistics */* from t
where x + 1 = 3;
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t where x + 1 = 3
Plan hash value: 2498539100
----------------------------------
| Id | Operation | Name |
----------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| T |
----------------------------------
select /*+ gather_plan_statistics */* from t
where case when x = 2 then x else 1 end = 2;
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t where case when x = 2
then x else 1 end = 2
Plan hash value: 2498539100
----------------------------------
| Id | Operation | Name |
----------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| T |
----------------------------------
To overcome this, you need to create a function-based index. The index must have the exact same function you use in your where clause:
create index i1 on t (x + 1);
create index i2 on t (case when x = 2 then x else 1 end);
select /*+ gather_plan_statistics */* from t
where x + 1 = 3;
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t where x + 1 = 3
Plan hash value: 2702475061
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| T |
| 2 | INDEX RANGE SCAN | I1 |
--------------------------------------------
select /*+ gather_plan_statistics */* from t
where case when x = 2 then x else 1 end = 2;
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t where case when x = 2
then x else 1 end = 2
Plan hash value: 3121986482
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| T |
| 2 | INDEX RANGE SCAN | I2 |
--------------------------------------------
Of course, that's not the whole story. Oracle can use plain indexes on columns with functions applied if:
- It's a multi-column index and there are no functions on the other columns OR
- Your query only access the columns in the index:
drop index i1;
drop index i2;
create index i3 on t(y, x);
select /*+ gather_plan_statistics */* from t t
where y = 'XXXXXXXX'
and to_char(x) = '2';
X Y
---------- ----------
2 XXXXXXXX
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */* from t t where y = 'XXXXXXXX'
and to_char(x) = '2'
Plan hash value: 1086377500
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | INDEX FAST FULL SCAN| I3 |
-------------------------------------
drop index i3;
select /*+ gather_plan_statistics */x from t t
where to_char(x) = '2';
X
----------
2
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */x from t t where to_char(x) = '2'
Plan hash value: 53475921
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | INDEX FAST FULL SCAN| I |
-------------------------------------
In the second scenario, the column must be mandatory (not null):
alter table t modify x null;
select /*+ gather_plan_statistics */x from t t
where to_char(x) = '2';
X
----------
2
select * from table(dbms_xplan.display_cursor(null, null, 'BASIC LAST'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ gather_plan_statistics */x from t t where to_char(x) = '2'
Plan hash value: 2498539100
----------------------------------
| Id | Operation | Name |
----------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| T |
----------------------------------