Domanda

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.

È stato utile?

Soluzione

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top