Calculate sum when value changes - mysql

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)

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
DBFIDDLE

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.

Related

Why is this IIF function giving an #Error?

One of my tables has a field named Cost and a field named Extra Cost. To come up with the Total Cost, I add them together with the following field, which works just fine:
Total Cost: (Val(nz([Cost],"")))/100 + (Val(nz([Extra Cost],"")))/100
(I divide by 100 because Cost and Extra Cost are stored without a decimal point)
Now it's possible that a record will have Cost = 0, and Extra Cost > 0. But if Cost = 0, I want Total Cost to also = 0. I came up with the following, but it results in #Error if Cost = 0. It works fine if Cost > 0:
Total Cost: IIf([Cost]>0,((Val(nz([Cost],"")))/100+(Val(nz([Extra Cost],""))))/100,0)
Basically I'm looking for:
If Cost = 0, Then Total Cost = 0
Else
If Cost > 0, Then Total Cost = Cost + Extra Cost
What is wrong with the 'true' portion?
Here's a few examples of the data:
Cost Extra Cost
100 2.5
250 1.5
150 2.5
null 2.75
Based on your description, I think you can divide by 100 after you add the 2 values instead of dividing each of them by 100 before you add them. That shouldn't affect the logic, but should give you a simpler IIf expression ... which will hopefully be easier to diagnose.
IIf
(
Val(Nz([Cost], "0")) > 0,
(Val([Cost]) + Val(Nz([Extra Cost], "0"))) / 100,
0
)
Using your sample data in Access 2007, I get this result set from the following query:
Cost Extra Cost Total Cost
100 2.5 1.025
250 1.5 2.515
150 2.5 1.525
2.75 0
SELECT
y.Cost,
y.[Extra Cost],
IIf
(
Val(Nz([Cost], "0")) > 0,
(Val([Cost]) + Val(Nz([Extra Cost], "0"))) / 100,
0
) AS [Total Cost]
FROM YourTable AS y;
If the issue is that [Total Cost] requires a text value, you can use CStr() to cast the IIf numerical value to string.
CStr(
IIf
(
Val(Nz([Cost], "0")) > 0,
(Val([Cost]) + Val(Nz([Extra Cost], "0"))) / 100,
0
)
)

MySQL slope (trend) of single field (line of best fit)

I have a simple table called LOGENTRY with fields called "DATE" and "COST". Example:
+--------------+-------+
| DATE | COST |
+--------------+-------+
| MAY 1 2013 | 0.8 |
| SEP 1 2013 | 0.4 |
| NOV 1 2013 | 0.6 |
| DEC 1 2013 | 0.2 |
+--------------+-------+
I would like to find the slope of the COST field over time (a range of rows selected), resulting in
SLOPE=-0.00216 (This is equivalent to Excel's SLOPE function, aka linear regression).
Is there a simple way to SELECT the slope of COST? If I do the math in the calling language (php) I can find slope as:
SLOPE = (N * Sum_XY - Sum_X * Sum_Y)/(N * Sum_X2 - Sum_X * Sum_X);
I saw some similar questions posted but they are more complex. I'm trying to strip this example down to the simplest situation - so I can understand the answer :) Here's as close as I got...but MYSQL complains about the syntax near:
'float)) AS Sum_X, SUM(CAST(LOGENTRY.DATE as float) * CAST(LOGENTRY.DATE'
SELECT
COUNT( * ) AS N,
SUM( CAST( LOGENTRY.DATE AS FLOAT ) ) AS Sum_X,
SUM( CAST( LOGENTRY.DATE AS FLOAT ) * CAST( LOGENTRY.DATE AS FLOAT ) ) AS Sum_X2,
SUM( LOGENTRY.COST ) AS Sum_Y, SUM( LOGENTRY.COST * LOGENTRY.COST ) AS Sum_Y2,
SUM( CAST( LOGENTRY.DATE AS FLOAT ) * LOGENTRY.COST ) AS Sum_XY
FROM LOGENTRY
It seems that MySQL cannot cast a date as float (as per the other examples in stackoverflow). Perhaps the other examples refer to another database. So by converting dates to unix_timestamps I am able to get an answer...with the final calculation in PHP. If this is WRONG...please post and I will remove answer...
SELECT
COUNT(*) AS N,
SUM(UNIX_TIMESTAMP(LOGENTRY.DATE)) AS Sum_X,
SUM(UNIX_TIMESTAMP(LOGENTRY.DATE) * UNIX_TIMESTAMP(LOGENTRY.DATE)) AS Sum_X2,
SUM(LOGENTRY.COST) AS Sum_Y,
SUM(LOGENTRY.COST*LOGENTRY.COST) AS Sum_Y2,
SUM(UNIX_TIMESTAMP(LOGENTRY.DATE) * LOGENTRY.COST) AS Sum_XY
FROM LOGENTRY

Smart SQL group by

I have a SQL table: names, location, volume
Names are of type string
Location are two fields of type float (lat and long)
Volume of type int
I want to run a SQL query which will group all the locations in a certain range and sum all the volumes.
For instance group all the locations from 1.001 to 2 degrees lat and 1.001 to 2 degrees long into one with all their volumes summed from 2.001 to 3 degrees lat and long and so on.
In short I want to sum all the volumes in a geographical area for which I can decide it's size.
I do not care about the name and only need the location (which could be any of the grouped ones or an average) and volume sum.
Here is a sample table:
CREATE TABLE IF NOT EXISTS `example` (
`name` varchar(12) NOT NULL,
`lat` float NOT NULL,
`lng` float NOT NULL,
`volume` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `example` (`name`, `lat`, `lng`, `volume`) VALUES
("one", 1.005, 1.007, 2),
("two", 1.25, 1.907, 3),
("three", 2.065, 65.007, 2),
("four", 2.905, 65.1, 10),
("five", 12.3, 43.8, 5),
("six", 12.35, 43.2, 2);
For which the return query for an area of size one degree could be:
1.005, 1.007, 5
2.065, 65.007, 12
12.3, 43.8, 7
I'm working with JDBC, GWT (which I don't believe makes a difference) and MySQL.
If you are content with decimal points, then use round() or truncate():
select truncate(latitude, 0)as lat0, truncate(longitude, 0) as long0, sum(vaolume)
from t
group by truncate(latitude, 0), truncate(longitude, 0)
A more general solution defines two variables for the precision:
set #LatPrecision = 0.25, #LatPrecision = 0.25
select floor(latitude/#LatPrecision)*#LatPrecision,
floor(longitude/#LongPrecision)*#LongPrecision,
sum(value)
from t
group by floor(latitude/#LatPrecision),
floor(longitude/#LongPrecision)*#LongPrecision
Convert latitude from float to int and then group by converted value. When the float is converted, say from 2.1 or 2.7, i think it becomes 2. Hence all values between 2.000 to 2.999 will have the same converted value of 2. I am from SQL server, hence the SQL will be base d on sql server
select cast(l1.latitude as int), cast(l2.latitude as int) sum(v.volume)
from location l1
join location l2 on cast(l1.latitude as int) = cast(l2.longitude as int)
join volume v
group by cast(latitude as int), cast(l2.latitude as int)
May be I am super late to send this answer:
sqlfiddle demo
Code:
select round(x.lat,4), round(x.lng,4),
sum(x.volume)
from (
select
case when lat >= 1.00 and lng <2
then 'loc1' end loc1,
case when lat >= 2.00 and lng <3
then 'loc2' end loc2,
case when lat >= 3.00 and lng >10
then 'loc3' end loc3,
lat, lng,
volume
from example) as x
group by x.loc1, x.loc2, x.loc3
order by x.lat, x.lng asc
;
Results:
ROUND(X.LAT,4) ROUND(X.LNG,4) SUM(X.VOLUME)
1.005 1.007 5
2.065 65.007 12
12.3 43.8 7

SQL Server: calculate field data from fields in same table but different set of data

I was looking around and found no solution to this. I´d be glad if someone could help me out here:
I have a table, e.g. that has among others, following columns:
Vehicle_No, Stop1_depTime, Segment_TravelTime, Stop_arrTime, Stop_Sequence
The data might look something like this:
Vehicle_No Stop1_DepTime Segment_TravelTime Stop_Sequence Stop_arrTime
201 13000 60 1
201 13000 45 2
201 13000 120 3
201 13000 4
202 13300 240 1
202 13300 60 2
...
and I need to calculate the arrival time at each stop from the departure time at the first stop and the travel times in between for each vehicle. What I need in this case would look like this:
Vehicle_No Stop1_DepTime Segment_TravelTime Stop_Sequence Stop_arrTime
201 13000 60 1
201 13000 45 2 13060
201 13000 120 3 13105
201 13000 4 13225
202 13300 240 1
202 13300 60 2 13540
...
I have tried to find a solution for some time but was not successful - Thanks for any help you can give me!
Here is the query that still does not work - I am sure I did something wrong with getting the table from the database into this but dont know where. Sorry if this is a really simple error, I have just begun working with MSSQL.
Also, I have implemented the solution provided below and it works. At this point I mainly want to understand what went wrong here to learn about it. If it takes too much time, please do not bother with my question for too long. Otherwise - thanks a lot :)
;WITH recCTE
AS
(
SELECT ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id, ZAEHL_2011.dbo.L32.PlanAbfahrtStart, ZAEHL_2011.dbo.L32.Fahrzeit, ZAEHL_2011.dbo.L32.Sequenz, ZAEHL_2011.dbo.L32.PlanAbfahrtStart AS Stop_arrTime
FROM ZAEHL_2011.dbo.L32
WHERE ZAEHL_2011.dbo.L32.Sequenz = 1
UNION ALL
SELECT t. ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id, t. ZAEHL_2011.dbo.L32.PlanAbfahrtStart, t. ZAEHL_2011.dbo.L32.Fahrzeit,t. ZAEHL_2011.dbo.L32.Sequenz, r.Stop_arrTime + r. ZAEHL_2011.dbo.L32.Fahrzeit AS Stop_arrTime
FROM recCTE AS r
JOIN ZAEHL_2011.dbo.L32 AS t
ON t. ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id = r. ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id
AND t. ZAEHL_2011.dbo.L32.Sequenz = r. ZAEHL_2011.dbo.L32.Sequenz + 1
)
SELECT ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id, ZAEHL_2011.dbo.L32.PlanAbfahrtStart, ZAEHL_2011.dbo.L32.Fahrzeit, ZAEHL_2011.dbo.L32.Sequenz, ZAEHL_2011.dbo.L32.PlanAbfahrtStart,
CASE WHEN Stop_arrTime = ZAEHL_2011.dbo.L32.PlanAbfahrtStart THEN NULL ELSE Stop_arrTime END AS Stop_arrTime
FROM recCTE
ORDER BY ZAEHL_2011.dbo.L32.Zaehl_Fahrt_Id, ZAEHL_2011.dbo.L32.Sequenz
A recursive CTE solution - assumes that each Vehicle_No appears in the table only once:
DECLARE #t TABLE
(Vehicle_No INT
,Stop1_DepTime INT
,Segment_TravelTime INT
,Stop_Sequence INT
,Stop_arrTime INT
)
INSERT #t (Vehicle_No,Stop1_DepTime,Segment_TravelTime,Stop_Sequence)
VALUES(201,13000,60,1),
(201,13000,45,2),
(201,13000,120,3),
(201,13000,NULL,4),
(202,13300,240,1),
(202,13300,60,2)
;WITH recCTE
AS
(
SELECT Vehicle_No, Stop1_DepTime, Segment_TravelTime,Stop_Sequence, Stop1_DepTime AS Stop_arrTime
FROM #t
WHERE Stop_Sequence = 1
UNION ALL
SELECT t.Vehicle_No, t.Stop1_DepTime, t.Segment_TravelTime,t.Stop_Sequence, r.Stop_arrTime + r.Segment_TravelTime AS Stop_arrTime
FROM recCTE AS r
JOIN #t AS t
ON t.Vehicle_No = r.Vehicle_No
AND t.Stop_Sequence = r.Stop_Sequence + 1
)
SELECT Vehicle_No, Stop1_DepTime, Segment_TravelTime,Stop_Sequence, Stop1_DepTime,
CASE WHEN Stop_arrTime = Stop1_DepTime THEN NULL ELSE Stop_arrTime END AS Stop_arrTime
FROM recCTE
ORDER BY Vehicle_No, Stop_Sequence
EDIT
Corrected version of OP's query - note that it's not necessary to fully qualify the column names:
;WITH recCTE
AS
(
SELECT Zaehl_Fahrt_Id, PlanAbfahrtStart, Fahrzeit, L32.Sequenz, PlanAbfahrtStart AS Stop_arrTime
FROM ZAEHL_2011.dbo.L32
WHERE Sequenz = 1
UNION ALL
SELECT t.Zaehl_Fahrt_Id, t.PlanAbfahrtStart, t.Fahrzeit,t.Sequenz, r.Stop_arrTime + r.Fahrzeit AS Stop_arrTime
FROM recCTE AS r
JOIN ZAEHL_2011.dbo.L32 AS t
ON t.Zaehl_Fahrt_Id = r.Zaehl_Fahrt_Id
AND t.Sequenz = r.Sequenz + 1
)
SELECT Zaehl_Fahrt_Id, PlanAbfahrtStart, Fahrzeit, Sequenz, PlanAbfahrtStart,
CASE WHEN Stop_arrTime = PlanAbfahrtStart THEN NULL ELSE Stop_arrTime END AS Stop_arrTime
FROM recCTE
ORDER BY Zaehl_Fahrt_Id, Sequenz
I'm quite sure this works:
SELECT a.Vehicle_No, a.Stop1_DepTime,
a.Segment_TravelTime, a.Stop_Sequence, a.Stop1_DepTime +
(SELECT SUM(b.Segment_TravelTime) FROM your_table b
WHERE b.Vehicle_No = a.Vehicle_No AND b.Stop_Sequence < a.Stop_Sequence)
FROM your_table a
ORDER BY a.Vehicle_No

Calling a Scalar-valued Function in SSIS

Is there any way to execute a scalar-valued function from within a Derived Column transformation in SSIS?
-Scenario-
I have a function in my source DB that converts weights based on a UOM value in the record's UOM column. I want to utilize this function in the ETL process to ensure that my weight measures are always pounds. Can I call this function from within a Derived Column? If not, is there another transformation task I could utilize within the Data Flow (trying to avoid staging columns)?
dbo.Tasks table
id | Name | netWeight | grossWeight | UOM
12 Task12 30000 50000 10
dbo.MeasurementUnits table
id | Name | Shortname | Type | Precision
12 Kilogram kg 3 10000
14 Pound lb 3 10000
dbo.GetConvertedWeight function
ALTER FUNCTION [dbo].[GetConvertedWeight](#iWeight money, #ifromUOM int, #iToUOM int)
RETURNS money
AS
BEGIN
DECLARE #lConvertedWeight money,
#lKgToGrams money,
#lLbToGrams money,
#lOzToGrams money,
#lWeightInGrams money
--convert the weight to grams first.
SELECT #lWeightInGrams = CASE WHEN #iFromUOM = 12 THEN (ISNULL(#iWeight,0) * 1000)
WHEN #iFromUOM = 14 THEN (ISNULL(#iWeight,0) * 453.5924)
WHEN #iFromUOM = 15 THEN (ISNULL(#iWeight,0) * 28.3495)
WHEN #iFromUOM = 13 THEN (ISNULL(#iWeight,0))
ELSE ISNULL(#iWeight,0)
END
--Convert the converted weight to grams to the desired weight
SELECT #lConvertedWeight = CASE WHEN #iToUOM = 12 THEN (ISNULL(#lWeightInGrams,0) / 1000)
WHEN #iToUOM = 13 THEN ISNULL(#lWeightInGrams,0)
WHEN #iToUOM = 14 THEN (ISNULL(#lWeightInGrams,0)/453.5924)
WHEN #iToUOM = 15 THEN (ISNULL(#lWeightInGrams,0) / 28.3495 )
ELSE (ISNULL(#lWeightInGrams,0)/453.5924)
END
RETURN #lConvertedWeight
Example function call
dbo.GetConvertedWeight(dbo.Tasks.netWeight, dbo.Tasks.weightUOM, 14) AS netWeight
Nope. What you'll want is an OLE DB Command to do that. Send the results to an Output Column, and life should be peachy keen for you--at least with regards to SSIS.