Calculating differences with SQL - mysql

I have been doing some research on this subject for a while, and thanks to a solution posted in another topic, I got close to solving this issue.
I am attempting to get the changes in a column of data: row(n) - row(n-1)
update Table tt1
left outer JOIN Table tt2
on tt1.name = tt2.name
and tt1.date-tt2.date=1
set tt1.delta = (tt1.amount-ifnull(tt2.amount, tt1.amount));
Output is
Date | Value | Delta
2013-03-30| 38651 | 393
2013-03-31| 39035 | 384
2013-04-01| 39459 | 0
2013-04-02| 39806 | 347
As you can see, the difference does not calculate for the first of April (the rest of the values are just fine). The same happens for the 1st day of every month.
My guess is that there is something to do with [and tt1.date-tt2.date=1], but I can't figure out exactly what.
Thanks for all your help in advance!

I made some changes to your statement... your error is either on the way you handle the dates or in the way you handle the delta...
update Table tt1
left outer JOIN Table tt2
on tt1.name = tt2.name
and tt1.date = date_sub(tt2.date, interval 1 day)
set tt1.delta = case when tt2.amount is not null then tt1.amount - tt2.amount else -1 end;

try DATEDIFF this will give you the difference between two dates by days.
and DATEDIFF(tt1.date,tt2.date) =1
this because you are differenting 01-31 and its not true thats why you get 0
so you should defference also the month.

One guess would be that the date is not stored as date but rather has a time component. You can get around this by converting to date using date() or using datediff():
update Table tt1 left outer JOIN
Table tt2
on tt1.name = tt2.name and datediff(tt1.date, tt2.date) = 1
set tt1.delta = (tt1.amount-ifnull(tt2.amount, tt1.amount));

Related

Calculate running day difference in MySQL

I'd like tot calculate the difference in days from the current row, compared to the previous row. What I have now:
Here is my column is day difference
**Day_Diff**
351
363
363
But what I actually want:
**Day_Diff**
351
12
12
What query would I need to accomplish this?
This should do the work (if what you want is minimum in first row and then difference to the minimum, with table being the name of your table and day_diff the name of the column you named Day_diff):
See sqlfiddle :
SELECT
CASE WHEN t1.day_diff = t2.min_day_diff
THEN t1.day_diff
ELSE t1.day_diff - t2.min_day_diff
END
FROM mytable t1
LEFT JOIN (SELECT MIN(day_diff) AS min_day_diff FROM mytable) t2
ON True;

MySQL find minimum and maximum date associated with a record in another table

I am trying to write a query to find the number of miles on a bicycle fork. This number is calculated by taking the distance_reading associated with the date that the fork was installed on (the minimum reading_date on or after the Bicycle_Fork.start_date associated with the Bicycle_Fork record) and subtracting that from the date that the fork was removed (the maximum reading_date on or before the Bicycle_Fork.end_date or, if that is null, the reading closest to today's date). I've managed to restrict the range of odometer_readings to the appropriate ones, but I cannot figure out how to find the minimum and maximum date for each odometer that represents when the fork was installed. It was easy when I only had to look at records matching the start_date or end_date, but the user is not required to enter a new odometer reading for each date that a part is changed. I've been working on this query for several hours now, and I can't find a way to use MIN() that doesn't just take the single smallest date out of all of the results.
Question: How can I find the minimum reading_date and the maximum reading_date associated with each odometer_id while maintaining the restrictions created by my WHERE clause?
If this is not possible, I plan to store the values retrieved from the first query in an array in PHP and deal with it from there, but I would like to be able to find a solution solely in MySQL.
Here is an SQL fiddle with the database schema and the current state of the query: http://sqlfiddle.com/#!2/015642/1
SELECT OdometerReadings.distance_reading, OdometerReadings.reading_date,
OdometerReadings.odometer_id, Bicycle_Fork.fork_id
FROM Bicycle_Fork
INNER JOIN (Bicycles, Odometers, OdometerReadings)
ON (Bicycles.bicycle_id = Bicycle_Fork.bicycle_id
AND Odometers.bicycle_id = Bicycles.bicycle_id AND OdometerReadings.odometer_id = Odometers.odometer_id)
WHERE (OdometerReadings.reading_date >= Bicycle_Fork.start_date) AND
((Bicycle_Fork.end_date IS NOT NULL AND OdometerReadings.reading_date<= Bicycle_Fork.end_date) XOR (Bicycle_Fork.end_date IS NULL AND OdometerReadings.reading_date <= CURRENT_DATE()))
This is the old query that didn't take into account the possibility of the database lacking a record that corresponded with the start_date or end_date:
SELECT MaxReadingOdo.distance_reading, MinReadingOdo.distance_reading
FROM
(SELECT OdometerReadings.distance_reading, OdometerReadings.reading_date,
OdometerReadings.odometer_id
FROM Bicycle_Fork
LEFT JOIN (Bicycles, Odometers, OdometerReadings)
ON (Bicycles.bicycle_id = Bicycle_Fork.bicycle_id
AND Odometers.bicycle_id = Bicycles.bicycle_id AND OdometerReadings.odometer_id = Odometers.odometer_id)
WHERE Bicycle_Fork.start_date = OdometerReadings.reading_date) AS MinReadingOdo
INNER JOIN
(SELECT OdometerReadings.distance_reading, OdometerReadings.reading_date,
OdometerReadings.odometer_id
FROM Bicycle_Fork
LEFT JOIN (Bicycles, Odometers, OdometerReadings)
ON (Bicycles.bicycle_id = Bicycle_Fork.bicycle_id AND Odometers.bicycle_id
= Bicycles.bicycle_id AND OdometerReadings.odometer_id = Odometers.odometer_id)
WHERE Bicycle_Fork.end_date = OdometerReadings.reading_date) AS
MaxReadingOdo
ON MinReadingOdo.odometer_id = MaxReadingOdo.odometer_id
I'm trying to get the following to return from the SQL schema:
I will eventually sum these into one number, but I've been working with them separately to make it easier to check the values.
min_distance_reading | max_distance_reading | odometer_id
=============================================================
75.5 | 2580.5 | 1
510.5 | 4078.5 | 2
17.5 | 78.5 | 3
I don't understand the final part of the puzzle, but this seems close...
SELECT MIN(ro.distance_reading) min_val
, MAX(ro.distance_reading) max_val
, ro.odometer_id
FROM OdometerReadings ro
JOIN odometers o
ON o.odometer_id = ro.odometer_id
JOIN Bicycle_Fork bf
ON bf.bicycle_id = o.bicycle_id
AND bf.start_date <= ro.reading_date
GROUP
BY ro.odometer_id;
http://sqlfiddle.com/#!2/015642/8

Using MAX of a field to select a record returns MAX of the table ignores the condition

I'm doing a stored procedure for a report, and I'm trying to get only those records with the highest value of a determined field (accumulated amount), the thing is I can't seem to find the solution to this, the only solution that i've came up with is using an extra condition, the problem is the field changes every month (period) and not all the records are updated but I need to retrieve them all... (if an asset is depreciated there wont be anymore records relating that asset in that table)
I'm sorry if this is confusing, I'll try my best to explain
The report needs to have for each supplier registered a list of the assets that supplies, their description, their current location, it's price, and how much money still needs to be depreciated from the asset.
So, what I'm doing it's first getting the list of suppliers, then getting the list of assets associated with a location (Using cursors) then I try to calculate how much money needs to be depreciated, there's a table called 'DEPRECIACIONES' that stores the asset, the period, and how much money has been depreciated from that asset for each period and for each asset that hasn't been completely depreciated. The problem comes when I try to calculate the MAX amount of money depreciated for an asset and then selecting the row for that item that has that MAX amount, I'm sure i'm doing something wrong, my TSQL and general database knowledge is not good and I'm trying to learn by myself.
I've uploaded the schema, tables and the stored procedure that throws the wrong output here:
http://sqlfiddle.com/#!3/78c32
The Right output should be something like this:
Proveedor | Activo | Descripcion | Ubicacion Actual | Costo Adquisicion | Saldo sin depreciar | Periodo
Supplier | Asset | Description | Current Location | Cost | Money to be depreciated | Period
-------------------------------------------------------------------------------------------
Monse |ActivoT| texthere | 1114 |2034.50| RANDOM NUMBER HERE |RandomP
Monse |cesart | texthere | 4453 |4553.50| RANDOM NUMBER HERE |RandomP
nowlast | activ | texthere | 4453 |1234.65| RANDOM NUMBER HERE |RandomP
nowlast |augusto| texthere | 4450 |4553.50| RANDOM NUMBER HERE |RandomP
Sara |Activo | texthere | 1206 |746.65 | RANDOM NUMBER HERE |RandomP
I'd really appreciate telling me what i'm doing wrong (which is probably a lot) and how to fix it, thank you in advance.
Good skills in giving complete information via SqlFiddle.
I don't have a complete answer for you, but this may help.
Firstly, ditch the cursor - it's hard to debug and possibly slow. Refactor to a SELECT statement. This is my attempt, which should be logically equivalent to your code:
SELECT
p.Proveedor,
a.Activo,
a.Descripcion,
Ubi.Ubicacion,
saldo_sin_depreciar = a.Costo_adquisicion - d.Monto_acumulado,
d.Periodo
FROM
PROVEEDORES p
INNER JOIN ACTIVOS_FIJOS a ON a.Proveedor = p.Proveedor
INNER JOIN DEPRECIACIONES d ON a.Activo = d.Activo
INNER JOIN
(
SELECT
MAX(d1.Monto_acumulado) AS MaxMonto
FROM DEPRECIACIONES d1
INNER JOIN DEPRECIACIONES d2
ON d1.Monto_acumulado = d2.Monto_acumulado
) MaxAe
ON d.Monto_acumulado = MaxAe.MaxMonto
INNER JOIN ACTIVO_UBICACION Ubi ON a.activo = ubi.activo
INNER JOIN
(
SELECT
activo,
ubicacion,
Fecha_Ubicacion,
RowNum = row_number() OVER ( partition BY activo ORDER BY abs(datediff(dd, Fecha_Ubicacion, getdate())))
FROM
ACTIVO_UBICACION
) UbU
ON UbU.ubicacion = Ubi.Ubicacion
WHERE
-- a.Activo IS NOT NULL AND
UbU.RowNum = 1
ORDER BY
p.Proveedor
COMMENTS
I've moved the WHERE criteria that are defining the joins up into ON clauses in the table list, that makes it easier to see how you are joining the tables.
Note that all the joins are INNER, which may not be what you want - you may need some LEFT JOIN's, I don't understand the logic enough to say.
Also, in your cursor procedure the Ubi and UbU parts don't seem to explicitly join with the rest of the tables, so I've pencilled-in an INNER JOIN on the activo column, as this is the way the tables join in the FK relationship.
In your cursor code, you would effectively get a CROSS JOIN which is probably wrong and also expensive to run.
The WHERE clause a.Activo IS NOT NULL is not required, because the INNER JOIN ensures it.
Hope this helps you sort it out.
I ended up using another query for the cursor and fixed the problem. It's probably not optimal but it works. Whenever I learn more database related stuff I'll optimize it.
Here's the new query:
DECLARE P CURSOR STATIC
FOR SELECT a.Proveedor, actub.activo, actub.ubicacion FROM [SISACT].PROVEEDORES p,[SISACT].ACTIVOS_FIJOS a, (SELECT activo, ubicacion, Fecha_Ubicacion, row_number() OVER (
partition BY activo ORDER BY abs(datediff(dd, Fecha_Ubicacion, getdate()))
) AS RowNum FROM [SISACT].ACTIVO_UBICACION) actub WHERE RowNum = 1 AND a.Proveedor = p.Proveedor AND actub.activo = a.Activo
OPEN P
FETCH NEXT FROM P INTO #p, #a, #u
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #activo = a.Activo, #descripcion = a.Descripcion, #costo_adquisicion = a.Costo_adquisicion, #saldo_depreciado = MaxAe.MaxMonto, #periodo = d.Periodo
FROM [SISACT].ACTIVOS_FIJOS a, [SISACT].DEPRECIACIONES d, SISACT.PROVEEDORES pro, SISACT.ACTIVO_UBICACION actu, (SELECT MAX(d1.Monto_acumulado) AS MaxMonto FROM [SISACT].DEPRECIACIONES d1 INNER JOIN [SISACT].DEPRECIACIONES d2 ON d1.Monto_acumulado = d2.Monto_acumulado WHERE d1.Activo = #a AND d2.Activo = #a) MaxAe
WHERE a.Activo = d.Activo AND a.Activo = #a AND d.Activo = #a AND a.Proveedor = #p AND actu.Activo = #a AND actu.Ubicacion = #u
SET #saldo_sin_depreciar = #costo_adquisicion - #saldo_depreciado
FETCH NEXT FROM P INTO #p, #a, #u
END
CLOSE P
DEALLOCATE P

MySQL: self join to produce pairs of dates

I have a table with entries for Items as being 'lost' and 'found'. Each row has a date for the event. Im hoping to build a query with matching pairs of 'itemid', 'lost date', 'found date' by joining the table to itself.
This works to a point: unfortunately if there are multiple lost and found pairs for a given item each 'lost date' will be joined with all the 'found dates' that follow it.
Still with me?
The query goes something like:
select c0.ItemId, c0.ChangeDate, c1.ChangeDate from Changes c0
join Changes c1 on
c0.ItemId = c1.ItemId and c1.ChangeDate >= c0.ChangeDate
where c0.ChangeType = 9 (lost) and c1.ChangeType = 10 (found);
What Im hoping to achieve is some form of a given 'lost date' paired with only the next 'found date' in sequence (or NULL if no 'found date' exists). Im (pretty) sure this is possible but Im not seeing the path.
I was wondering about putting a sub-select in the first join and using a LIMIT 1 to get only one record but I don't see how to join this to the appropriate row in the main part of the select. MySQL tells me it doesn't exist. Fair enough.
The trick here is to stipulate 'and there is no other lost or found date between the lost and found dates', or, in SQL:
SELECT c0.ItemId, c0.ChangeDate, c1.ChangeDate
FROM Changes AS c0
JOIN Changes AS c1 ON c0.ItemId = c1.ItemId AND c1.ChangeDate >= c0.ChangeDate
WHERE c0.ChangeType = 9 -- Lost
AND c1.ChangeType = 10 -- Found
AND NOT EXISTS(SELECT *
FROM Changes AS c2
WHERE c2.ItemId = c1.ItemID
AND c2.ChangeType IN (9, 10) -- Lost or Found
AND c2.ChangeDate BETWEEN c0.ChangeDate AND c1.ChangeDate
AND (c2.ChangeDate != c0.ChangeDate AND c2.ChangeDate != c1.ChangeDate)
);
Because that is a correlated sub-query, it tends to slow down the query, but it should produce the correct rows.
There is an important caveat about the way I've eliminated the c0 and c1 rows by stipulating that the ChangeDate for the row in c2 should be different from either the lost date or the found date. However, the main query seems to allow for an item to be found on the same day that it is lost. There might be some other column - such as a ChangeId column - that is not mentioned in the query yet that could be used instead:
AND c2.ChangeID NOT IN (c0.ChangeID, c1.ChangeID)
You'll need to think about what happens if an item is lost on, say, 2011-06-07, and lost again on 2011-06-14, and only found on 2011-06-21. And what about if it is also found on 2011-06-28? Such problems should be prevented by the data entry processing, so the query above assumes there won't be such issues.
Generally when dealing with pairs of dates (e.g. start/end for scheduling) the advice is don't put them on separate rows. Put them in two columns of the same row. See Joe Celko's SQL Programming Style.
But that said, you can solve it with your current schema by searching doing another self-join to search for a ChangeDate between the two. If none is found (that is, if c2.* is null because of the outer join), then c0 and c1 are "adjacent."
select c0.ItemId, c0.ChangeDate, c1.ChangeDate
from Changes c0
inner join Changes c1 on
c0.ItemId = c1.ItemId and c1.ChangeDate > c0.ChangeDate
left outer join Changes c2 on
c0.ItemId = c2.ItemId and c2.ChangeDate > c0.ChangeDate
and c2.ChangeDate < c1.ChangeDate
and c2.ChangeType IN (9,10) -- edit
where c0.ChangeType = 9 (lost) and c1.ChangeType = 10 (found)
and c2.ItemId IS NULL;
In the above example, I've assumed that ChangeDate is unique, and I changed the >= to >. If ChangeDate is not unique, you'll have to come up with some other expression to test for c2 "between" c0 and c1.

SELECT to get two entries, from same table, differentiated by date, in one row

I have a table in which i keep different meters (water meter, electricity meter) and in another table i keep the readings for each meter.
The table structure is like this :
The meter table
MeterID | MeterType | MeterName
The readings Table:
ReadingID | MeterID | Index | DateOfReading
The readings for a meter are read monthly. The thing I am trying to do now is to get the Meter information, the current reading and the previous reading in just one row. So if i would have a query, the following row would result:
MeterID | MeterType | MeterName | CurrnetIndex | LastIndex
I have the following query so far :
SELECT Meter.MeterID, Meter.MeterType, Meter.MeterName, CurrentReading.Index, PreviousReading.Index
FROM Meters AS Meter
LEFT OUTER JOIN Readings AS CurrentReading ON Meter.MeterID = CurrentReading.MeterID
LEFT OUTER JOIN Readings AS PreviousReading ON Meter.MeterID = PreviouseReading.MeterID
WHERE CurrentReading.ReadingID != PreviousReading.ReadingID AND DIMESTAMPDIFF(MONTH, CurrentReading.DateOfReading, PreviousReding.DateOfReading)=-1
The problem is that I may not have the current reading or the previous, or both, but I would still need to have the meter information retrieved. It is perfectly acceptable for me to get NULL columns, but i still need a row :)
Use:
SELECT m.meterid,
m.metertype,
m.metername,
current.index,
previous.index
FROM METER m
LEFT JOIN READING current ON current.meterid = m.meterid
AND MONTH(current.dateofreading) = MONTH(NOW())
LEFT JOIN READING previous ON previous.meterid = m.meterid
AND MONTH(current.dateofreading) = MONTH(NOW())-1
Being an OUTER JOIN - if the MONTH filtration is done in the WHERE clause, it can produce different results than being done in the ON clause.
You could use a subquery to grab the value from a month ago:
select *
, (
select Index
from Readings r2
where r2.MeterID = m.MeterID
and DIMESTAMPDIFF(MONTH, r1.DateOfReading,
r2.DateOfReading) = -1
) as LastIndex
from Meter m
left join
Readings r1
on r1.MeterID = m.MeterID
Another solution is to allow the second left join to fail. You can do that by just changing your where clause to:
WHERE PreviousReading.ReadingID is null
or
(
CurrentReading.ReadingID != PreviousReading.ReadingID
and
DIMESTAMPDIFF(MONTH, CurrentReading.DateOfReading,
PreviousReding.DateOfReading) = -1
)
well, sql philosophy is to store what you know. if you don't know it, then there isn't any row for it. if you do a filter on the record set that you search for, and find nothing, then there isn't any month reading for it. Or that i didnt understand the question