Question

In my Oracle 11g database I have two tables for an order system. One table describes a discount, another describes a penalty, on orders within a given range of amounts.

Table DISCOUNTS:

| FROM_AMOUNT  | TO_AMOUNT | DISCOUNT
| 25           | 39        |  2%
| 40           | 49        |  5%
| 50           | infinty   | 10%

Table PENALTIES:

| FROM_AMOUNT  | TO_AMOUNT | PENALTY
| 0            |  9        | 10%
| 10           | 20        |  5%

These values are changed from time to time.

Since there is no good reason why these are separate tables, I have created a DISCOUNTPENALTY view, unioning these tables:

select discount.from_amount as from_amount, 
       discount.to_amount as to_amount, 
       discount.discount * -1 as change
union
select penalty.from_amount as from_amount, 
       penalty.to_amount as to_amount, 
       penalty.penalty as change

giving

| FROM_AMOUNT  | TO_AMOUNT | CHANGE
| 50           | infinity  | -10%
| 40           | 49        |  -5%
| 25           | 39        |  -2%
| 10           | 20        |   5%
|  0           |  9        |  10%

As you can see, the ranges are not overlapping, so for (almost) any given amount I can do a

select CHANGE from DISCOUNTPENALTY where :amount >= FROM_AMOUNT and :amount <= TO_AMOUNT

and get the correct discount/penalty. With the notable exception if :amount is in the range 21-24.

In my data it is implied that an amount in the range 21-24 gives 0 discount/penalty, since that range is not listed in either DISCOUNTS or PENALTIES tables.

I would like to reflect that fact in my DISCOUNTPENALTY view, so that it will include a row for 21-24:

| FROM_AMOUNT  | TO_AMOUNT | CHANGE
| 50           | infinity  | -10%
| 40           | 49        |  -5%
| 25           | 39        |  -2%
| 21           | 24        |   0% <-- Not from any table 
| 10           | 20        |   5%
|  5           |  9        |  10%

How do I go about having my view include this row? I suspect some analytical functions might be needed, but I'm not sure where to start.

Although a view is what I would prefer, it might not the most practical approach to this, so I'm open to other solutions. Note that I'm not able to alter the source tables in any way.

Was it helpful?

Solution

You don't need the view.

This should do what you want (change the 2 literal to a variable, I tested it w/ a 2).

The first query grabs the discount if there's a discount. The second (connected by union) would grab a penalty if there's a penalty, but of an amount above the first row's from_amount, and the third (connected by union) would grab the penalty if there is one and it's below the first row's from_amount.

You can test it here: http://sqlfiddle.com/#!4/d41d8/25188/0

with discounts as
( select 25 as from_amount, 39 as to_amount, .02 as discount from dual union all
  select 40 as from_amount, 49 as to_amount, .05 as discount from dual union all
  select 50 as from_amount, 99999 as to_amount, .10 as discount from dual  ) 
   , penalties as
( select 5 as from_amount, 9 as to_amount, .10 as penalty from dual union all
  select 10 as from_amount, 19 as to_amount, .05 as penalty from dual)
select discount as change
from discounts
where 2 between from_amount and to_amount
union all
select -penalty as change
from penalties
where 2 between from_amount and to_amount
union all
select -penalty as change
from penalties
where 2 < (select min(from_amount) from penalties)
and from_amount = (select min(from_amount) from penalties)

Regarding your last edit, the query below would show "0" for any amount for which there is neither a penalty nor a discount (the version of my query above would just show no rows for such a situation). You may prefer that it show zero, like this:

select discount as change
from discounts
where 22 between from_amount and to_amount
union all
select -penalty as change
from penalties
where 22 between from_amount and to_amount
union all
select -penalty as change
from penalties
where 22 < (select min(from_amount) from penalties)
and from_amount = (select min(from_amount) from penalties)
union all
select 0 as change
from dual
where not exists (select 1 from discounts where 22 between from_amount and to_amount)
  and not exists (select 1 from penalties where 22 between from_amount and to_amount)
  and 22 >= (select min(from_amount) from penalties)

If you change the SQL for that view to the below, you should get the range in between to show zero:

select discounts.from_amount as from_amount,
       discounts.to_amount as to_amount,
       discounts.discount * -1 as change
  from discounts
union
select penalties.from_amount as from_amount,
       penalties.to_amount   as to_amount,
       penalties.penalty     as change
  from penalties
union
select p.to_amount + 1, d.from_amount - 1, 0 as change
  from discounts d, penalties p
 where d.from_amount = (select min(from_amount) from discounts) and
 p.to_amount = (select max(to_amount) from penalties)
 order by from_amount desc
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top