I need to return all records if input parameter is null.
I\'ve written a simple query
declare
l_sql varchar2(100);
i number := 1;
begin
l_sq
Simply use coalesce
. It is the most readable and understandable way to write this. Since the logic is contained in one predicate, it's easier to maintain and remove:
select * from job where id = coalesce(:i, id)
As requested, a 'proof' this actually uses the index:
create table x ( id number(15) null );
create unique index x_pk on x( id );
select id
from x
where id = coalesce(:x, id)
; -- Uses index
select id
from x
where id = :x or :x is null
; -- Full table scan
Plan:
SELECT STATEMENT ALL_ROWS Cost: 1 Bytes: 13 Cardinality: 1
1 INDEX FULL SCAN INDEX (UNIQUE) X_PK Cost: 1 Bytes: 13 Cardinality: 1
Please let me show the full story behind the Val's question.
We need to produce the report from the Job table which has following fields :
The report filter form produces the input parameters for the report stored procedure.
Among other parameters there are two check list boxes on that filter form to check the required Missed Reason items or/and FolowUp Reason items.
As you can see MissedReasonId and FollowUpReasonId are nullable therefore if user doesn't check any item in those check list boxes we need to show all the jobs.
If some of the items are checked we send them to the stored procedure as a delimited ID list.
We are trying to get the data using one single query.
The assumption is :
There's no problem with the ID list itself. We can convert it into table or an array and make selection working fast enough. There's no problem if the ID list is mandatory on the filter form (we don't need to check the parameter for null). The problem arises then it is not mandatory (parameter can be null) and there are multiple filter conditions like that on the filter form.
The query we are using looks like this :
Select j.Id , j.JobNumber .....
From Job j
left join MissedReason mr on mr.ID = j.MissedReasonId
left join FollowUpReason fr on fr.ID = j.FollowUpReasonId
....
Where
(j.JobDate between P_StartDate and P_EndDate)
and ( P_MissedReasonIdList is null or (j.MissedReasonId "IS IN THAT P_MissedReasonIdList"))
and ( P_FollowUpReasonIdList is null or (j.FollowUpReasonId "IS IN THAT MissedReasonIdList"))
and ......
We expected that if an input parameter is null in the first part of the WHERE condition :
"and ( P_MissedReasonIdList is null or (j.MissedReasonId "IS IN THAT P_MissedReasonIdList"))"
Oracle shouldn't bother to check the second part of the condition. (This is what Val mentioned in his question. Right Val ?)
I really don't want to build the dynamic query on the fly including only those conditions which should be checked into query.
But is there any other solution to our problem ?
IS NULL
suppresses the index
usage. Because NULL
values are not indexed.
There are two ways to make the use of index with IS NULL
:
1.BITMAP
index. However, more applicable in OLTP
systems.
2.My favourite way, and nice to demonstrate. We could make the leaves of the b-tree index a constant
. Thus, making use of index while querying for NULL
. Basically, the NULLs are all together, at the top/bottom of the index. Oracle can use the index forwards or backwards
, so doesn't really matter. And it does a full scan of the index
.
I have answered a similar question here http://www.orafaq.com/forum/mv/msg/194746/625371/#msg_625371
The first scenario won't use the index due to the OR is null condition :
SQL> SELECT * FROM PROD_NEW; PROFILE_TYPE --------------- Prod Prodparallel Prod SQL> CREATE INDEX PROD_NEW_I1 ON PROD_NEW 2 (PROFILE_TYPE 3 ); Index created. SQL> EXPLAIN PLAN FOR SELECT * FROM PROD_NEW WHERE PROFILE_TYPE = 'Prod' OR PROFILE_TYPE IS NULL; Explained. SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 2121244107 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 3 | 15 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| PROD_NEW | 3 | 15 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - filter("PROFILE_TYPE" IS NULL OR "PROFILE_TYPE"='Prod') 13 rows selected
Let's make the leaves constant :
SQL> DROP INDEX PROD_NEW_I1; Index dropped. SQL> CREATE INDEX PROD_NEW_I1 ON PROD_NEW 2 (PROFILE_TYPE,1 3 ); Index created. SQL> EXPLAIN PLAN FOR SELECT * FROM PROD_NEW WHERE PROFILE_TYPE = 'Prod' OR PROFILE_TYPE IS NULL; Explained. SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 1272076902 -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | 15 | 1 (0)| 00:00:01 | |* 1 | INDEX FULL SCAN | PROD_NEW_I1 | 3 | 15 | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - filter("PROFILE_TYPE" IS NULL OR "PROFILE_TYPE"='Prod') 13 rows selected. SQL>