Reputation: 1195
I'm making a car statistic solution where I need to charge per kilometer driven.
I have the following table:
table: cars
columns: car_id, km_driven
table: pricing
columns: from, to, price
Content in my cars
table can be:
car_id, km_driven
2, 430
3, 112
4, 90
Content on my pricing
table can be:
from, to, price
0, 100, 2
101, 200, 1
201, null, 0.5
Meaning that the first 100 km cost 2USD per km, the next 100 km cost 1USD per km and everything above costs 0.5USD per km.
Is there a logic and simple way to calculate cost for my cars
via PostgreSQL
?
So if a car
has driven ex. 201, then the price would be 100x2 + 100x1 + 0.5
, not simply 201x0.5
.
Upvotes: 2
Views: 123
Reputation: 22514
I would definitely do this using a procedure, as it can be implemented in a very straightforward manner using loops. However, you should be able to do something similar to this:
select car_id, sum(segment_price)
from (
select
car_id,
km_driven,
f,
t,
price,
driven_in_segment,
segment_price
from (
select
car_id,
km_driven,
f,
t,
price,
(coalesce(least(t, km_driven), km_driven) - f) driven_in_segment,
price * (coalesce(least(t, km_driven), km_driven) - f) segment_price
from
-- NOTE: cartesian product here
cars,
pricing
where f < km_driven
)
) data
group by car_id
order by car_id
I find that pretty less readable, though.
UPDATE:
That query is a bit more complex than necessary, I was trying out some things with window functions that were not needed in the end. A simplified version here that should be equivalent:
select car_id, sum(segment_price)
from (
select
car_id,
km_driven,
f,
t,
price,
(coalesce(least(t, km_driven), km_driven) - f) driven_in_segment,
price * (coalesce(least(t, km_driven), km_driven) - f) segment_price
from
-- NOTE: cartesian product here
cars,
pricing
where f < km_driven
) data
group by car_id
order by car_id
Upvotes: 1
Reputation: 1270011
I would write the query as:
select c.car_id, c.km_driven,
sum(( least(p.to_km, c.km_driven) - p.from_km + 1) * p.price) as dist_price
from cars c join
pricing p
on c.km_driven >= p.from_km
group by c.car_id, c.km_driven;
Here is a db<>fiddle.
Upvotes: 2
Reputation: 383
Modified from @sean-johnston's answer:
select
car_id, km_driven,
sum(case
when km_driven>=start then (least(finish,km_driven)-start+1)*price
else 0
end) as dist_price
from cars,pricing
group by car_id,km_driven
fiddling a bit more, case can be omitted when where is in place
select
car_id, km_driven,
sum((least(finish,km_driven)-start+1)*price) as dist_price
from cars,pricing
where km_driven >= start
group by car_id,km_driven
Upvotes: 2
Reputation: 174
Judicious use of case/sum combinations. However, firstly need to make your ranges consistent. I'll choose to change the first range to 1,100. Given that then the following should give you want you're after. (I've also used start/finish as from/to are reserved words).
select
car_id, km_driven,
sum (case
when finish is null and km_driven >= start
then (km_driven-start+1) * price
when km_driven >= start
then (case
when (km_driven - start + 1) > finish
then (finish - start + 1)
else (km_driven - start + 1)
end) * price
else 0
end) as dist_price
from cars, pricing
where km_driven >= start
group by 1, 2;
Explanation:
If you don't want to (or can't) make your ranges consistent then you'd need to add a third outer case for the start range.
Upvotes: 1
Reputation: 31993
you can use join and calculate your cost by using case when
select c.car_id, case when p.price=.5
then 100*2+100*1+(c.km_driven-200)*0.5
when p.price=1 then 100*2+(c.km_driven-100)*1
else c.km_driven*p.price as cost
from cars c join pricing p
on c.km_driven>=p.from and c.km_driven<=p.to
Upvotes: 0