问题
I use the following trick to be able to index a column that has some nulls:
create index xx_people_idx1 on xx_people(id_number, -1);
This works great. Sadly this does not help for when you use optional parameters though:
select *
from xx_people
where id_number = nvl(:p_id_number, id_number);
This results in a full table scan, even when you provide a value for p_id_number
. Is there a trick to use the index in this situation?
Since searching by id number and name are my only 2 searches this is very desirable.
回答1:
The NVL
trick ought to work and allow index access. In fact, NVL
is generally the best way to do this, and usually works better than other conditions involving CASE
or OR
. I've used the NVL
trick many times and the simple test case below shows that it can use an index.
Schema
create table xx_people(id_number number, a number, b number);
insert into xx_people
select level, level, level from dual connect by level <= 100000;
commit;
begin
dbms_stats.gather_table_stats(user, 'xx_people');
end;
/
create index xx_people_idx1 on xx_people(id_number, -1);
Generate Execution Plan
explain plan for
select *
from xx_people
where id_number = nvl(:p_id_number, id_number);
select * from table(dbms_xplan.display);
Execution Plan
Plan hash value: 3301250992
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100K| 3808K| 106 (1)| 00:00:01 |
| 1 | VIEW | VW_ORE_67373E14 | 100K| 3808K| 106 (1)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
|* 3 | FILTER | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| XX_PEOPLE | 1 | 15 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | XX_PEOPLE_IDX1 | 1 | | 2 (0)| 00:00:01 |
|* 6 | FILTER | | | | | |
|* 7 | TABLE ACCESS FULL | XX_PEOPLE | 100K| 1464K| 103 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(:P_ID_NUMBER IS NOT NULL)
5 - access("ID_NUMBER"=:P_ID_NUMBER)
6 - filter(:P_ID_NUMBER IS NULL)
7 - filter("ID_NUMBER" IS NOT NULL)
That plan is a bit confusing at first. But it's got the best of both worlds; the filter operation allows Oracle to decide at run time to use a full table scan when the bind variable is null (and all rows are returned), and an index when the bind variable is not null (and only a few rows are returned).
This all means that there is probably something weird happening in your specific case. You may need to post a fully reproducible test case for us to figure out why an index isn't used.
来源:https://stackoverflow.com/questions/49351568/oracle-index-use-with-optional-parameters