I am trying to build a system that will track vehicle fuelings, and have run into a problem with one report; determining fuel efficiency in distance/fuel. Sample data is:
odometer | fuel | partial_fillup |
---|---|---|
61290 | 10.3370 | 0 |
61542 | 6.4300 | 0 |
61735 | 4.3600 | 0 |
61994 | 7.5000 | 0 |
62242 | 5.4070 | 0 |
62452 | 8.1100 | 0 |
62713 | 5.7410 | 1 |
62876 | 9.4850 | 0 |
63243 | 6.1370 | 1 |
63499 | 10.7660 | 0 |
Where odometer is the total distance the vehicle has traveled, fuel is the number of gallons or liters put in, and partial_fillup is a boolean meaning the fuel tank was not completely filled if non-zero.
If the user fills the tank each time the query I can use is:
set @a = null;
select
odometer,
odometer-previousOdometer distance,
fuel,
(odometer-previousOdometer)/fuel mpg,
partial_fillup
from
(
select
@a as previousOdometer,
@a:=odometer,
odometer,
fuel/1000 fuel,
partial_fillup
from fuel
where
vehicle_id =1
and odometer >= 61290
order by odometer
) as readings
where readings.previousOdometer is not null;
However, when the user only partially fills the tank, the correct procedure would be to subtract the last full fueling from current odometer reading, then divide by the sum of all fuel since the previous odometer reading, so at odometer 63499, the calculate would be (63499-62876)/(10.7660 6.1370)
CodePudding user response:
This will get the average used on the last ride:
select
odometer,
odometer-lag(odometer) over (order by odometer) as distance,
fuel,
(odometer-lag(odometer) over (order by odometer))/fuel as mpg
from fuel
output:
odometer | distance | fuel | mpg |
---|---|---|---|
61290 | 10.3370 | ||
61542 | 252 | 6.4300 | 39.1913 |
61735 | 193 | 4.3600 | 44.2661 |
61994 | 259 | 7.5000 | 34.5333 |
62242 | 248 | 5.4070 | 45.8665 |
62452 | 210 | 8.1100 | 25.8940 |
62713 | 261 | 5.7410 | 45.4625 |
62876 | 163 | 9.4850 | 17.1850 |
63243 | 367 | 6.1370 | 59.8012 |
63499 | 256 | 10.7660 | 23.7786 |
Or you can calculate the total drive distance, and the total amount of fuel used:
select
distance,
sum_fuel,
distance/sum_fuel as mpg
from (
select
f.odometer,
f.odometer-(select min(odometer) from fuel) as distance,
fuel,
sum_fuel
from fuel f
inner join (
select
odometer,
sum(fuel) over (order by R) as sum_fuel
from (
select
odometer,
fuel,
row_number() over (order by odometer) R
from fuel) x
) x on x.odometer = f.odometer
) x2
which will get next output, which will get closer to an average after a longer time of measurement:
distance | sum_fuel | mpg |
---|---|---|
0 | 10.3370 | 0.0000 |
252 | 16.7670 | 15.0295 |
445 | 21.1270 | 21.0631 |
704 | 28.6270 | 24.5922 |
952 | 34.0340 | 27.9720 |
1162 | 42.1440 | 27.5721 |
1423 | 47.8850 | 29.7170 |
1586 | 57.3700 | 27.6451 |
1953 | 63.5070 | 30.7525 |
2209 | 74.2730 | 29.7416 |
CodePudding user response:
I was able to figure it out after studying Luuk's answer. I'm sure there is a more efficient way to do this; I am not used to using variables in SQL. But, the answers are correct in the test data.
set @oldOdometer = null;
set @totalFuel = 0;
select
s.odometer,
format(fuel, 3) fuel,
s.distance,
format( distance / fuel, 2) as mpg
from (
select
partial_fillup as partial,
odometer,
(fuel @totalFuel) as fuel,
@totalFuel as totalFuel,
@oldOdometer oldOdometer,
if ( partial_fillup, null,odometer - @oldOdometer ) as distance,
@totalFuel := if ( partial_fillup, @totalFuel fuel, 0) as pastFuel,
@oldOdometer := if (partial_fillup,@oldOdometer,odometer ) as runningOdometer
from
fuel
order by
odometer ) s
where s.distance is not null
order by s.odometer
limit 1,999;
limit 1,999 simply there to skip the first row returned, since there is not enough data to calculate distance or mpg. On my copy of MySQL, doing this means you do not need to initialize the two variables (you don't have to include the set commands at the beginning), so it works with my reporting tool very well. If you do initialize them, you do not need the limit statement. Works assuming you don't have more than 999 rows returned.