Skip to Main Content
  • Questions
  • Conditional execution one of two parts of union all query

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Alan.

Asked: April 12, 2018 - 2:13 pm UTC

Last updated: April 13, 2018 - 1:08 pm UTC

Version: 12.1.0.2

Viewed 10K+ times! This question is

You Asked

I have have two complex, expensive queries that I have performed a union all to bring together for use in a report tool



I have a bind variable that, if set to one value, the union query should execute the first part of the union. If the bind variable is set to a second value, then the second part of the union query should be executed.



However, when I look at trace and/or explain plans for my query, there is a cost coming from BOTH parts of the union query.



Here is a simplified version of what I am trying to do :


    create table t1   
    as  
    select 'T1' as ttype, 'Gadget 1' as descr from dual  
    union all  
    select 'T2' as ttype, 'Gadget 2' as descr from dual  
    union all  
    select 'T3' as ttype, 'Gadget 3' as descr from dual;  
      
    create table t2   
    as  
    select 'T21' as ttype, 'Widget 1' as descr from dual  
    union all  
    select 'T22' as ttype, 'Widget 2' as descr from dual  
    union all  
    select 'T23' as ttype, 'Widget 3' as descr from dual;  
    3' as ttype, 'Widget 3' as descr from dual;  



And then my query is :


    select ttype, descr from t1  
    where :P_Type='1'  
    union all  
    select ttype, descr from t2  
    where :P_Type='2';  



I would expect that if I provide bind value of '1' for P_Type, then the CBO / query would only actually access table t1 (with zero cardinality / cost for table t2).



However, what I see is a cost & cardinality coming from BOTH tables, regardless of the value I assign to the bind variable



How can I force the CBO / database to "prune" those parts of the query where the condition / bind value indicates that part of the union query is irrelevant ?



I do not want to split it into two different queries, as there is a lot of formatting on the report, which I would need to replicate for the two queries.



Many thanks in advance for your feedback ...



Oracle Database Enterprise Edition 12.1.0.2 with SQL Tuning option

and Chris said...

Are you looking at the estimated rows in the explain plan, or the actual rows in an execution plan?

Remember that explain plan costs and cardinalities are estimates. These are generated with no knowledge of what the bind values are.

Bind variable peeking allows the optimizer to adapt the plan based on the inputs. But in this case it doesn't matter. The plan is the same whatever value you assign to :P_Type.

For example, in the execution plan below, notice how starts and A-rows are both zero for T2.

The filter operations at steps 2 & 4 short-circuit processing. The database only does the full scans below them if the condition is true:

create table t1 as  
  select 'T1' as ttype, 'Gadget 1' as descr from dual  
  union all  
  select 'T2' as ttype, 'Gadget 2' as descr from dual  
  union all  
  select 'T3' as ttype, 'Gadget 3' as descr from dual;  
    
create table t2 as  
  select 'T21' as ttype, 'Widget 1' as descr from dual  
  union all  
  select 'T22' as ttype, 'Widget 2' as descr from dual  
  union all  
  select 'T23' as ttype, 'Widget 3' as descr from dual;  
    
set serveroutput off

var p_type varchar2(1);
exec :p_type := '1';

select /*+ gather_plan_statistics */ttype, descr from t1  
where :P_Type='1'  
union all  
select ttype, descr from t2  
where :P_Type='2';

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS LAST +COST'));

----------------------------------------------------------------------------   
| Id  | Operation           | Name | Starts | E-Rows | Cost (%CPU)| A-Rows |   
----------------------------------------------------------------------------   
|   0 | SELECT STATEMENT    |      |      1 |        |     4 (100)|      3 |   
|   1 |  UNION-ALL          |      |      1 |        |            |      3 |   
|*  2 |   FILTER            |      |      1 |        |            |      3 |   
|   3 |    TABLE ACCESS FULL| T1   |      1 |      3 |     2   (0)|      3 |   
|*  4 |   FILTER            |      |      1 |        |            |      0 |   
|   5 |    TABLE ACCESS FULL| T2   |      0 |      3 |     2   (0)|      0 |   
----------------------------------------------------------------------------

Predicate Information (identified by operation id):                            
---------------------------------------------------                            
                                                                               
   2 - filter(:P_TYPE='1')                                                     
   4 - filter(:P_TYPE='2')  


So it has pruned the execution of the query!

Remember: to evaluate a plan, you must look at the actual work it does.

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Alan Lawlor, April 12, 2018 - 4:45 pm UTC

I believe I am using the actual plan, rather than estimated :

SET AUTOTRACE ON
var p_type varchar2(1);
exec :p_type := '1';
select ttype, descr from t1
where :P_type='1'
union all
select ttype, descr from t2
where :P_Type='2';


Resulting output :

Autotrace Enabled
Shows the execution plan as well as statistics of the statement.

PL/SQL procedure successfully completed.


TTY DESCR   
--- --------
T11 Gadget 1
T12 Gadget 2
T13 Gadget 3

Explain Plan
-----------------------------------------------------------

PLAN_TABLE_OUTPUT                                                                                                                                                                                                                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 697940467
 
----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |  1310 | 19650 |     6  (50)| 00:00:01 |
|   1 |  UNION-ALL          |      |       |       |            |          |
|*  2 |   FILTER            |      |       |       |            |          |
|   3 |    TABLE ACCESS FULL| T1   |   655 |  9825 |     3   (0)| 00:00:01 |
|*  4 |   FILTER            |      |       |       |            |          |
|   5 |    TABLE ACCESS FULL| T2   |   655 |  9825 |     3   (0)| 00:00:01 |

PLAN_TABLE_OUTPUT                                                                                                                                                                                                                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter(:P_TYPE='1')
   4 - filter(:P_TYPE='2')

Statistics
-----------------------------------------------------------
               1  CPU used by this session
               1  CPU used when call started
               4  Requests to/from client
               3  consistent gets
               3  consistent gets from cache
               3  consistent gets pin
               3  consistent gets pin (fastpath)
               4  global enqueue gets sync
               4  global enqueue releases
               3  non-idle wait count
               2  opened cursors cumulative
               1  opened cursors current
               1  pinned cursors current
               3  session logical reads
               5  user calls


Thanks, Chris & Connor

Alan Lawlor, April 13, 2018 - 8:25 am UTC

Now I get it. The output from autotrace is misleading, in that it is still only looking at explain plan estimates, even though it has executed the actual query.

Only viewing the explain plan from the cursor can we see the actual plan.

Thanks very much
Chris Saxon
April 13, 2018 - 1:08 pm UTC

You got it!

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.