Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Donald.

Asked: November 03, 2017 - 11:26 pm UTC

Last updated: November 21, 2017 - 1:27 pm UTC

Version: Oracle 12

Viewed 1000+ times

You Asked

I have an Argos report that takes a query(s) and output a report based on those entries. My problem is there are 5 possible querys. I trying to use and/or ( I already tried the CASE function) to pulls data base on their entry. The queries are; Receipt #, User_ID, Amount, Code, and Date range. IT could be Receipt only, or a combination of any of the other queries. below is a sample

<select distinct TBB.RECEIPT_NUMBER as "Receipt",
TBB_USER as "ID",
TBB_NAME as "Name",
TBB_DESC as "Description",
TBR_TRANS_DATE as "Trans_Date",
TBR_FEED_DOC_CODE as "Feed_Code",
TBR_AMOUNT as "Amount",
TBR_PAY_DETAIL_CODE as "Pay_Code",
TBR_CHARGE_DETAIL_CODE as "Charge_Code"
from tbb,
tbr
where :f1_parm_BT_runButton = 1 and
( TBB_RECEIPT_NUMBER = :f1_parm_EB_Receipt and TBB_RECEIPT_NUMBER = tbr_receipt_number and :f1_parm_EB_Receipt <> 0)
or (tbb_user = :f1_parm_eb_bannerid1 and tbb_user = tbr_user and :f1_parm_eb_bannerid1 <> ' ' )
or (tbr_feed_doc_code = :f1_parm_EB_FeedCode and tbr_receipt_number = TBB_RECEIPT_NUMBER and :f1_parm_EB_FeedCode<> ' ' )
or ( tbr_amount = :f1_parm_eb_amount1 and tbr_receipt_number = TBB_RECEIPT_NUMBER and :f1_parm_eb_amount1 <> 0)
or (tbr_pay_detail_code = :f1_parm_EB_AcctCode and tbr_receipt_number = TBB_RECEIPT_NUMBER and :f1_parm_EB_AcctCode <> ' ')
or (tbr_charge_detail_code = :f1_parm_EB_AcctCode and tbr_receipt_number = TBB_RECEIPT_NUMBER and :f1_parm_EB_AcctCode <> ' ')
or (trunc(tbr_trans_date) between trunc(to_date(:f1_parm_DT_startdate), 'DDD') and trunc(to_date(:f1_parm_DT_enddate), 'DDD' ) and trunc(tbb_activity_date) = trunc(tbr_trans_date)
) >
any suggestions?

and Connor said...

Generic one-size-fits-all queries generally end up performing badly for all permutations.

This is one of those cases where dynamic SQL is your friend.

So you have a concept where you:

- collect passed parameters into a bind variable array
- build the SQL piecemeal fashion
- execute it based on the number of binds you have

type bindlist is table of varchar2(100) index by pls_integer;
bind bindlist;

l_sql := 
'select distinct TBB.RECEIPT_NUMBER as "Receipt",
TBB_USER as "ID",
TBB_NAME as "Name",
TBB_DESC as "Description",
TBR_TRANS_DATE as "Trans_Date",
TBR_FEED_DOC_CODE as "Feed_Code",
TBR_AMOUNT as "Amount",
TBR_PAY_DETAIL_CODE as "Pay_Code",
TBR_CHARGE_DETAIL_CODE as "Charge_Code"
from tbb,
tbr'

if :f1_parm_EB_Receipt is not null then
  l_sql := l_sql || ' and TBB_RECEIPT_NUMBER = :1';
  bind(bind.count+1) := f1_parm_EB_Receipt;
end if;

if :f1_parm_eb_bannerid1 is not null then
  l_sql := l_sql || ' and tbb_user = :2';
  bind(bind.count+1) := f1_parm_eb_bannerid1;
end if;

if :f1_parm_EB_AcctCode is not null then
  l_sql := l_sql || ' and tbr_charge_detail_code = to_number(:3)';
  bind(bind.count+1) := f1_parm_EB_Receipt;
end if;

...
...



if bind.count = 1 then
  open my_cursor for l_sql using bind(1);
elsif bind.count = 2 then
  open my_cursor for l_sql using bind(1),bind(2);
elsif bind.count = 3 then
  open my_cursor for l_sql using bind(1),bind(2),bind(3);
elsif bind.count = 4 then
  open my_cursor for l_sql using bind(1),bind(2),bind(3),bind(4);
...  
  




Rating

  (7 ratings)

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

Comments

Conditional params

A reader, November 05, 2017 - 4:37 am UTC

Basically you can do something like:
WHERE 
  (:param1 = column1 OR :param1 is null)
  AND
  (:param2 = column2 OR :param2 is null)


Connor McDonald
November 06, 2017 - 1:07 am UTC

True, but its then a very difficult task for the optimizer to come up with a good plan for all scenarios

Donald Smith, November 06, 2017 - 6:37 pm UTC

I understand the concept, but I am having difficulty getting the TYPE bindlist TABLE to be accepted. What am I missing?
Do I need to run SQL in a procedure or with SQL>
Connor McDonald
November 07, 2017 - 2:07 am UTC

I've had another think about this. We can probably do it easier.

If the parameter HAS a value, then add:

'column = :parameter'

to the query. If it does NOT have a value, then add:

':parameter is null'

to the query.

In that way, you will always have the same number of binds, and hence not need a bind array. So your code ends up like:

l_sql := 
'select distinct TBB.RECEIPT_NUMBER as "Receipt",
TBB_USER as "ID",
TBB_NAME as "Name",
TBB_DESC as "Description",
TBR_TRANS_DATE as "Trans_Date",
TBR_FEED_DOC_CODE as "Feed_Code",
TBR_AMOUNT as "Amount",
TBR_PAY_DETAIL_CODE as "Pay_Code",
TBR_CHARGE_DETAIL_CODE as "Charge_Code"
from tbb,
tbr'

if :f1_parm_EB_Receipt is not null then
  l_sql := l_sql || ' and TBB_RECEIPT_NUMBER = :1';
else
  l_sql := l_sql || ' and :1 is null';
end if;

if :f1_parm_eb_bannerid1 is not null then
  l_sql := l_sql || ' and tbb_user = :2';
else  
  l_sql := l_sql || ' and :2 is null';
end if;

if :f1_parm_EB_AcctCode is not null then
  l_sql := l_sql || ' and tbr_charge_detail_code = :3';
else
  l_sql := l_sql || ' and :3 is null';
end if;

open my_cursor for l_sql using f1_parm_EB_Receipt, f1_parm_eb_bannerid1, f1_parm_EB_AcctCode;


so the number of binds is constant, but the optimizer will "filter out" all the static "is null" conditions at optimization time. So

Followup in

Donald Smith, November 14, 2017 - 6:48 pm UTC

This code is very helpful. One more question can I use this code you provided in a procedure where I can pass values (ex. :f1_parm_EB_Receipt) ?
Connor McDonald
November 15, 2017 - 1:59 am UTC

Yes. It will work - I only used the bind notation (colon) because your original question was phrased that way

GJ, November 15, 2017 - 1:00 pm UTC

Hi

There was an article on oracle magazine on binding a fixed number of inputs

Basically it made use of the following pseudo code
Eg:
if :param1 is not null then
l_sql = l_sql||'(col1=:param1)'
else
l_sql = l_sql||'(1=1 or :param1 is null)'
end if

http://www.oracle.com/technetwork/issue-archive/2009/09-jul/o49asktom-090487.html

Had a question here, would the use of
(1=1 or :param1 is null) optimize the code better than
(:param1 is null)

the rationale being, (1=1) being always true would cause the optimizer to not check if the condition :param1 is null is true?
Connor McDonald
November 16, 2017 - 1:23 pm UTC

It just changes where the evaluation is made

SQL> set autotrace traceonly explain
SQL> select * from dual
  2  where (1=1 or :x is null );

Execution Plan
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     2 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------

SQL>
SQL> select * from dual
  2  where :x is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 3752461848

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     2 |     2   (0)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(:X IS NULL)


But that difference will be negligible

Muliple queries

Donald Smith, November 16, 2017 - 10:55 pm UTC

This was most helpful my different queries base on passed input using dynamic sql runs like a charm.

I have a question not exactly relate to this question but how to I use the dbms_output.put_line command to write out the columns generated from the procedure call??
Connor McDonald
November 18, 2017 - 2:22 pm UTC

columns or rows ?

Hmm

Gh, November 19, 2017 - 6:47 am UTC

"I've had another think about this. We can probably do it easier....."

Well this solution in thé second post works Only if
API *asked* for such parameters to be null.
Usually when one build a multiple conditions say by wanting all employees for dept 10 without asking for say a specific region. That does not mean region should be null. Otherwise query returms no rows. Except if we are able to know in advance whether user had forced the item to be null.
Select * from dept where dept =10
(And if you care about region) and region ='North

So if issuer didn't specify a region that is that he didn't care about and not wanting those within region is null.

Think about it.
Connor McDonald
November 20, 2017 - 1:34 am UTC

Usually when one build a multiple conditions say by wanting all employees for dept 10 without asking for say a specific region. That does not mean region should be null

The solution does not do that.

It does not generate:

where dept=10
and region is null

It generates

where dept=10
and ( region = :region or :region is null )

We check for nullness in the *parameter* not the column

But..

Gh, November 20, 2017 - 6:28 am UTC

But thats what you did

if :f1_parm_EB_Receipt is not null then l_sql := l_sql || ' and TBB_RECEIPT_NUMBER = :1'; else l_sql := l_sql || ' and :1 is null'; end if;


Connor McDonald
November 21, 2017 - 1:27 pm UTC

Read the entire thread

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library