Reputation: 99
I'm trying to make automatic sum of average consumption of fuel using PHP and MYSQL. But I do not know how to do it. This is explanation:
Tabe CONSUM:
ID CARID LI KM DATETIME AVERAGE
--------------------------------------------------------------
6 9 70.17 174857 2015-02-14 12:58:51 9.44
5 5 51.00 154785 2015-02-13 10:11:19 8.73
4 8 99.44 485627 2015-02-12 11:45:48 6.84
3 9 47.78 174114 2015-02-11 10:21:32 /first entry
2 8 24.74 484175 2015-02-10 10:28:37 /first entry
1 5 89.65 154201 2015-02-09 10:01:14 /first entry
*Data as an example how I want to look like. Everything works except AVERAGE colum, that's why I'm here.
I'm trying to make php function that will make sum for every new entry of new and last KM entry with same CAREID something like this (exemple for CARID 9) :
I spent a lot of time trying to make this working but simply I never been even close.
Upvotes: 3
Views: 498
Reputation: 33808
You have two errors in your approach, which introduces complexity.
Any column that can be derived, such as your AVERAGE should not be stored.
If it is stored, it constitutes a duplicate column ... which leads to an Update Anomaly, as you are experiencing. The point of Normalisation is to eliminate data duplication, and thus eliminate Update Anomalies. It also eliminates complex code such as this, as well as triggers, etc.
Calculate SUM(), AVG(), etc, in the result set only, on the fly.
Use of ID columns, which basically means you have a Record Filing System, no a Relational Database. Without enumerating the many problems that it causes (I have done that elsewhere), just naming the problem here
The ID is a physical record pointer, it does not provide row uniqueness, as required for Relational Databases.
The ID is a physical record pointer, it means nothing, the user should not see it. But you (and others) have given it meaning.
Which glues you to the physical structure of the file, rather than the logical structure of the data. Which in turn complicates your code.
Therefore, without giving you a corrected CREATE TABLE
command, leaving yours as is, let us pretend that the ID, and the AVERAGE, do not exist in the file.
A third item, not related to approach, it seems that from the figure given, 10.58, you want Kilometres per litre, whereas the arithmetic you have detailed (Litres per 100 Km) will produce 9.44. If you do want an average of some kind, you are better off figuring out the elements first.
(Code obsolete due to revision)
I was attempting to obtain the figures you gave, while the question remained confused (note the comments to that effect). Since you have Revised your question, the requirement is now clear. It now appears you want (a) Litres per 100 Km [still not an "average"], and (b) an overall figure for each record [a kind of running total]. In that case, use this code.
The notes above remain valid and applicable.
SELECT CARID,
DATETIME,
KM,
LI,
LPCK = ( LI_TOT / ( ( KM_LAST-KM_FIRST / 100 ) ) -- not stored
FROM (
-- create a Derived Table with KM_FIRST
SELECT CARID,
DATETIME,
-- not stored
KM_FIRST = (
SELECT MIN( KM ) -- get the first KM for car
FROM CONSUM
WHERE CARID = C.CARID
),
KM_LAST = (
SELECT MAX( KM ) -- get the last KM for car
FROM CONSUM
WHERE CARID = C.CARID
),
KM, -- KM for this row
LI, -- LI for this row
LI_TOT = (
SELECT SUM( LI ) -- get the total LI for car
FROM CONSUM
WHERE CARID = C.CARID
AND KM != ( -- exclude first LI for car
SELECT MIN( KM ) -- get the first KM for car
FROM CONSUM
WHERE CARID = C.CARID
)
)
FROM CONSUM C
) AS CONSUM_EXT
ORDER BY CARID,
DATETIME
Notice I am manipulating the data, and only the data, no physical fields, we shouldn't care about the physical aspects of the file. Litres per 100 Km (what you are calling AVERAGE) is not stored, and there an Update Anomaly is avoided. The overall figure for each record is calculated "on the fly", at display time only.
This also eliminates your /first entry
issue.
Of course, CARID
as well is meaningless to the user.
Please feel free to comment or ask questions, etc.
There are many problems with storing a value that can be derived. This is Hard-coding at the data storage level. Sure, you can use a trigger to ease the pain, but it still won't work, because (a) the principle is broken and (b) it breaches existing engineering principles. Eg. what happens when the LI for a single row is incorrectly entered (eg. 700.17), and subsequently corrected (eg. 70.17)? All the subsequent rows for that car are now incorrect, and have to be re-calculated, and updated. So now you need an Update trigger as well as an Insert trigger. Cancer compounds itself.
The concept of an Update Anomaly, the prohibition of storing values that can be derived, have been with us since 1970, for good reason. We avoid them, for good reason.
Upvotes: 5
Reputation: 9010
It seems to me that the appropriate way to do this is with a BEFORE INSERT
trigger. Such a trigger may look like this:
delimiter //
create trigger avg_calc before insert on consum
for each row
begin
declare lastOdo int; -- variable to hold the last odometer reading
select km
into lastOdo -- store the last reading here
from consum
where carid = NEW.carid -- for the carid we are inserting
order by `datetime` desc -- get the last one by date
limit 1;
set NEW.average = (NEW.km - lastOdo) / NEW.li; -- update the average we're about to insert
end//
delimiter ;
This will then automatically average the last two entries per car every time a new entry is inserted for that car.
Upvotes: 2
Reputation: 12433
Your AVERAGE
where CARID=5
& CARID=8
does not compute the same as where CARID=9
, so my example does not exactly match, but if you are trying to this on an insert, you could do something like
INSERT INTO CONSUM
SELECT
6,
9,
70.17,
174857,
'2015-02-14 12:58:51',
ROUND((174857-a.KM)/70.17, 2)
FROM CONSUM a
WHERE a.CARID = 9
ORDER BY ID DESC
LIMIT 1;
sqlfiddle example - http://sqlfiddle.com/#!9/dce1d/1
Upvotes: 0
Reputation: 910
I'll post anyway. My idea was :
.
<?php
include("./inc.connect.php");
$Query = "SELECT id, km, li
FROM consum
WHERE cardid = 9";
$users = $db->query($Query);
$array_res = $users->fetchAll();
$nb_rows = count($array_res);
$diff_km = $array_res[($nb_rows - 1)]['km'] - $array_res[($nb_rows - 2)]['km'];
$new_li = number_format(($array_res[($nb_rows - 1)]['li'] / ($diff_km * 0.01)),2);
print "<pre>";
print_r($array_res);
print "</pre>";
echo "diff km : " . $diff_km . " new_li : " . $new_li . "<br>";
$UpdateQuery = "UPDATE consum SET average = '$new_li' WHERE id = " .
$array_res[($nb_rows - 1)]['id'];
/* Begin a transaction, turning off autocommit */
try
{
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->beginTransaction();
$sth = $db->exec($UpdateQuery);
$db->commit();
}
catch (Exception $e)
{
$db->rollBack();
echo "Failed: " . $e->getMessage();
}
?>
Result :
Array
(
[0] => Array
(
[id] => 3
[0] => 3
[km] => 174114
[1] => 174114
[li] => 47.78
[2] => 47.78
)
[1] => Array
(
[id] => 6
[0] => 6
[km] => 174857
[1] => 174857
[li] => 70.17
[2] => 70.17
)
)
diff km : 743 new_li : 9.44
UPDATE consum SET average = '9.44' WHERE id = 6
I did the maths - It's correct 70.17/7.43 = 9.44
Upvotes: 1
Reputation: 1270513
The following query gets the last id for each car:
select c.*,
(select c2.id
from consum c2
where c2.carid = c.carid and c2.id < c.id
order by c2.id desc
limit 1
) as last_id
from consum c;
Next, for the information you want, you can join back to the table to get the full record and then do the calculation:
select c.ID, c.CARID, c.LI, c.KM, c.DATETIME,
c.li / (c.km - cprev.km) / 100) as avg
from (select c.*,
(select c2.id
from consum c2
where c2.carid = c.carid and c2.id < c.id
order by c2.id desc
limit 1
) as last_id
from consum c
) c left join
consum cprev
on c.last_id = cprev.id;
Upvotes: 1