Oddly enough, in this particular case two combined crosses can help.
Take a look at the example below. Data table:
select * from all_tables; drop table Purchases; create table Purchases as select zx.object_id + (lev-1) * 100000 purchase_id, object_name purchase_name, round( dbms_random.value( 1, 200 )) purchase_price, zx.* from all_objects zx cross join (select level lev from dual connect by level <= 170); create unique index purchases_id_ix on Purchases( Purchase_id ); exec dbms_stats.gather_table_stats( user, 'Purchases' ); select count(*) from Purchases; COUNT(*) ---------- 10316620
Request:
var Purchase_id varchar2( 4000 ) var Purchase_name varchar2( 4000 ) var Purchase_price varchar2( 4000 ) begin :Purchase_id := '1139'; :Purchase_name := NULL; :Purchase_price := NULL; end; / explain plan for select p.* from Purchases p cross join ( select 1 from dual d where :Purchase_id is not null ) part_1 where Purchase_id = to_number( :Purchase_id ) and ( :Purchase_name is null or Purchase_name = :Purchase_name ) and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) union all select p.* from Purchases p cross join ( select 1 from dual d where :Purchase_id is null ) part_2 where ( :Purchase_name is null or Purchase_name = :Purchase_name ) and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) ;
Explanation Plan:
Plan hash value: 460094106 ------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 1 | NESTED LOOPS | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 2 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 | | 3 | VIEW | VW_JF_SET$96C1679A | 28259 | 5546K| 54091 (1)| 00:10:50 | | 4 | UNION-ALL | | | | | | |* 5 | FILTER | | | | | | |* 6 | TABLE ACCESS BY INDEX ROWID| PURCHASES | 1 | 132 | 3 (0)| 00:00:01 | |* 7 | INDEX UNIQUE SCAN | PURCHASES_ID_IX | 1 | | 2 (0)| 00:00:01 | |* 8 | FILTER | | | | | | |* 9 | TABLE ACCESS FULL | PURCHASES | 28258 | 3642K| 54088 (1)| 00:10:50 | ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 5 - filter(:PURCHASE_ID IS NOT NULL) 6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) 7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID)) 8 - filter(:PURCHASE_ID IS NULL) 9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) 27 wierszy zosta│o wybranych.
Test for: Purchase_id <> NULL
SQL> set pagesize 0 SQL> set linesize 200 SQL> set timing on SQL> set autotrace traceonly SQL> SQL> begin 2 :Purchase_id := '163027'; 3 :Purchase_name := NULL; 4 :Purchase_price := NULL; 5 end; 6 / Procedura PL/SQL zosta│a zako˝czona pomyťlnie. Ca│kowity: 00:00:00.00 SQL> select p.* 2 from Purchases p 3 cross join ( 4 select 1 from dual d 5 where :Purchase_id is not null 6 ) part_1 7 where Purchase_id = to_number( :Purchase_id ) 8 and ( :Purchase_name is null or Purchase_name = :Purchase_name ) 9 and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) 10 union all 11 select p.* 12 from Purchases p 13 cross join ( 14 select 1 from dual d 15 where :Purchase_id is null 16 ) part_2 17 where 18 ( :Purchase_name is null or Purchase_name = :Purchase_name ) 19 and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) 20 ; Ca│kowity: 00:00:00.09 Plan wykonywania ---------------------------------------------------------- Plan hash value: 460094106 ------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 1 | NESTED LOOPS | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 2 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 | | 3 | VIEW | VW_JF_SET$96C1679A | 28259 | 5546K| 54091 (1)| 00:10:50 | | 4 | UNION-ALL | | | | | | |* 5 | FILTER | | | | | | |* 6 | TABLE ACCESS BY INDEX ROWID| PURCHASES | 1 | 132 | 3 (0)| 00:00:01 | |* 7 | INDEX UNIQUE SCAN | PURCHASES_ID_IX | 1 | | 2 (0)| 00:00:01 | |* 8 | FILTER | | | | | | |* 9 | TABLE ACCESS FULL | PURCHASES | 28258 | 3642K| 54088 (1)| 00:10:50 | ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 5 - filter(:PURCHASE_ID IS NOT NULL) 6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) 7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID)) 8 - filter(:PURCHASE_ID IS NULL) 9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) Statystyki ---------------------------------------------------------- 1 recursive calls 0 db block gets 4 consistent gets 2 physical reads 0 redo size 1865 bytes sent via SQL*Net to client 519 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Test for: Purchase_id = NULL
SQL> begin 2 :Purchase_id := NULL; 3 :Purchase_name := 'DBMS_CUBE_UTIL'; 4 :Purchase_price := NULL; 5 end; 6 / Procedura PL/SQL zosta│a zako˝czona pomyťlnie. Ca│kowity: 00:00:00.00 SQL> select p.* 2 from Purchases p 3 cross join ( 4 select 1 from dual d 5 where :Purchase_id is not null 6 ) part_1 7 where Purchase_id = to_number( :Purchase_id ) 8 and ( :Purchase_name is null or Purchase_name = :Purchase_name ) 9 and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) 10 union all 11 select p.* 12 from Purchases p 13 cross join ( 14 select 1 from dual d 15 where :Purchase_id is null 16 ) part_2 17 where 18 ( :Purchase_name is null or Purchase_name = :Purchase_name ) 19 and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) ) 20 ; 510 wierszy zosta│o wybranych. Ca│kowity: 00:00:11.90 Plan wykonywania ---------------------------------------------------------- Plan hash value: 460094106 ------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 1 | NESTED LOOPS | | 28259 | 5546K| 54093 (1)| 00:10:50 | | 2 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 | | 3 | VIEW | VW_JF_SET$96C1679A | 28259 | 5546K| 54091 (1)| 00:10:50 | | 4 | UNION-ALL | | | | | | |* 5 | FILTER | | | | | | |* 6 | TABLE ACCESS BY INDEX ROWID| PURCHASES | 1 | 132 | 3 (0)| 00:00:01 | |* 7 | INDEX UNIQUE SCAN | PURCHASES_ID_IX | 1 | | 2 (0)| 00:00:01 | |* 8 | FILTER | | | | | | |* 9 | TABLE ACCESS FULL | PURCHASES | 28258 | 3642K| 54088 (1)| 00:10:50 | ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 5 - filter(:PURCHASE_ID IS NOT NULL) 6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) 7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID)) 8 - filter(:PURCHASE_ID IS NULL) 9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE))) Statystyki ---------------------------------------------------------- 0 recursive calls 0 db block gets 197993 consistent gets 82655 physical reads 0 redo size 16506 bytes sent via SQL*Net to client 882 bytes received via SQL*Net from client 35 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 510 rows processed
To know the real execution times, do not look at the plans, they lie, contain only estimates (as the oracle believes). Look at the lines with "Ca│kowity", this means "Total Runtime" (I don’t know how to change the code page to English in sqlplus). Also look at the "consistent data", this is a series of logically consistent blocks that reads the request.
First request (purchase_id <> null)
Ca│kowity: 00:00:00.09 4 consistent gets 2 physical reads
obviously it uses an index, time is 90 ms
Second request (purchase_id = null)
Ca│kowity: 00:00:11.90 197993 consistent gets 82655 physical reads
This query performs a full table scan.