SQL query (MAX-MIN) is showing wrong results - mysql

I am trying to calculate data from my database but first I've noticed strange behavior from the results I get, second, I have trouble making a request that take into account refills.
I have a table with :
Name - DateTime - content
I want to group by day the rows and select the difference of the number to have the consumption.
For example :
Name - DateTime - Content
Foo - 22-04-2018 6:00 - 120
Foo - 22-04-2018 10:00 - 119
Foo - 22-04-2018 16:00 - 118
The content has decreased, the result should be -2.
Output of my request = -2
Another example :
Name - DateTime - Content
Foo - 23-04-2018 6:00 - 50
Foo - 23-04-2018 10:00 - 90
Foo - 23-04-2018 16:00 - 120
Here we can notice that the number has increased. It means that instead of a consumption, we have refilled the reserve and the content has increased.
The result should be -70.
Output of my request : 30
My request :
SELECT day,
Abs(Sum(diffn)) AS totN
FROM (SELECT Date(datetime) AS day,
Max(content) - Min(content) AS diffN
FROM logs
WHERE NAME = 'Foo'
AND datetime >= '2018-04-22 00:00:00'
AND datetime <= '2018-04-23 00:00:00'
GROUP BY Date(datetime)) a
GROUP BY day;
But for the second example I have 30 as a result instead of 70, I don't know why...
I would like your help to change my request and take refills into account so that I get the results I want.
Thanks!

You need to determine the Prefix by comparing the highest and the lowest value, the time (hour) included. I'm using the 'CASE' function with two subqueries here.
Maybe you'll need to turn the year-month-day around, because I'm using the german datetime-format.
SET #datetime = '2018-04-22';
SELECT date(datetime) as day
,(CASE WHEN
(SELECT content FROM logs WHERE date(datetime) = #datetime ORDER BY datetime LIMIT 1)
>
(SELECT content FROM logs WHERE date(datetime) = #datetime ORDER BY datetime desc LIMIT 1)
THEN min(content) - max(content)
ELSE max(content) - min(content) END) as diffN
FROM logs
WHERE Name = 'Foo' AND date(datetime) = #datetime
GROUP BY day(datetime)
ORDER BY datetime
;

This should do the job:
SELECT day(datetime) as day, max(content) - min(content) as diffN
FROM logs
WHERE Name = 'Foo'
AND datetime >= '2018-04-23 00:00:00'
AND datetime <= '2018-04-24 00:00:00'
GROUP BY day(datetime)
Also, change the date filters it should be betweeen 23 and 24.

It might be that you need to establish the first and last datetime and their associated content. For example
drop table if exists t;
create table t (name varchar(3), dt datetime, content int);
insert into t values
('Foo' , '2018-04-22 06:00:00', 120),
('Foo' , '2018-04-22 10:00:00', 119),
('Foo' , '2018-04-22 16:00:00', 118),
('Foo' , '2018-04-23 06:00:00', 50),
('Foo' , '2018-04-23 10:00:00', 90),
('Foo' , '2018-04-23 16:00:00', 120);
select s.name,lastinday,firstinday,lastinday - firstinday
from
(
select name,dt, content lastinday
from t
where dt = (Select max(dt) from t t1 where t1.name = t.name and date(t1.dt) = date(t.dt))
) s
join
(
select name,dt, content firstinday
from t
where dt = (Select min(dt) from t t1 where t1.name = t.name and date(t1.dt) = date(t.dt))
) t
on t.name = s.name and date(t.dt) = date(s.dt);
+------+-----------+------------+------------------------+
| name | lastinday | firstinday | lastinday - firstinday |
+------+-----------+------------+------------------------+
| Foo | 118 | 120 | -2 |
| Foo | 120 | 50 | 70 |
+------+-----------+------------+------------------------+
2 rows in set (0.00 sec)

Why are you grouping it second time:
Ideally this should work:
SELECT Date(datetime) AS day,
Max(content) - Min(content) AS diffN
FROM logs
WHERE NAME = 'Foo'
AND datetime >= '2018-04-22 00:00:00'
AND datetime <= '2018-04-23 00:00:00'
GROUP BY Date(datetime)
Result of this query will contain only 2 rows - 1 for 22th and 1 for 23rd day. There is no need of grouping it again by day

Related

Find the matched rows based on redeem value in MySQL

I am implementing the cashback functionality with expiry feature. I am trying to redeem the partial amount based on early expiry date. I've already ordered the rows based on expiry date with the following mysql command.
SELECT * FROM `cashback` WHERE `user_id` = 1 and `used`= 'NO' AND IF(CONCAT(`point_expiry`) !='0000-00-00 00:00:00', `point_expiry` >= NOW(), NOW()) ORDER BY (case when CONCAT(`point_expiry`) = '0000-00-00 00:00:00' then 9999
else 1
end) ASC, `point_expiry` ASC
And the output for the following will be
id
amount
point_expiry
used
user_id
3
30
2023-02-24 00:00:00
NO
1
1
20
2023-02-25 00:00:00
NO
1
2
50
0000-00-00 00:00:00
NO
1
Now i want to redeem the value based on the above query result
Let say i want to redeem 35$ for the above result and the expected result will be
id
amount
point_expiry
used
used_amount
3
30
2023-02-24 00:00:00
NO
30
1
20
2023-02-25 00:00:00
NO
5
Here used_amount column represent the specific redeem value($35) redeemed based on amount column
Much appreciate your help!
This uses SUM(amount) OVER(ORDER BY ...) to calculate a running total and compares it to the balance -
SELECT *
FROM (
SELECT
`id`,
`amount`,
`point_expiry`,
`used`,
`amount` - GREATEST(SUM(`amount`) OVER (ORDER BY IF(`point_expiry` = '0000-00-00 00:00:00', 1, 0) ASC, `point_expiry` ASC, id ASC) - /* Here is your amount --> */ 35, 0) AS `used_amount`
FROM `cashback`
WHERE (`point_expiry` >= NOW() OR `point_expiry` = '0000-00-00 00:00:00')
AND `used` = 'NO'
AND `user_id` = 1
) t
WHERE `used_amount` > 0;

Query to subtract same column value at different interval of day with SQL database

In MySQL, I want to subtract one of column value at different interval of time based on another column 'timestamp'.
table structure is :
id | generator_id | timestamp | generated_value
1 | 1 | 2019-05-27 06:55:20 | 123456
2 | 1 | 2019-05-27 07:55:20 | 234566
3 | 1 | 2019-05-27 08:55:20 | 333456
..
..
20 | 1 | 2019-05-27 19:55:20 | 9876908
From above table I want to fetch the generated_value column value which should be difference of first timestamp fo day and timestamp of last value of day.
In above example I am looking query which should give me output as 9,753,452 (9876908 - 123456).
In general to fetch the single record of first value and last value of day I use below query
// Below will give me end day value
SELECT * FROM generator_meters where generator_id=1 and timestamp like '2019-05-27%' order by timestamp desc limit 1 ;
//this will give me last day value
SELECT * FROM generator_meters where generator_id=1 and timestamp like '2019-05-27%' order by timestamp limit 1 ;
Question is how should I get the final generated_value by doing minus of first value of day from last value of day.
Expected Output
generator_id | generated_value
1 | 9753452
Thanks in advance !!
In your example the value gets bigger and bigger. If this is guaranteed to be so, you can use
select max(generated_value) - min(generated_value) as result
from sun_electric.generator_meters
where generator_id = 1
and date(timestamp) = date '2019-05-27';
Or for multiple IDs:
select generator_id, max(generated_value) - min(generated_value) as result
from sun_electric.generator_meters
and date(timestamp) = date '2019-05-27'
group by generator_id
order by generator_id;
If the value is not ascending, then you can use the following query for ID 1:
select last_row.generated_value - first_row.generated_value as result
from
(
select *
from sun_electric.generator_meters
where generator_id = 1
and date(timestamp) = date '2019-05-27'
order by timestamp
limit 1
) first_row
cross join
(
select *
from sun_electric.generator_meters
where generator_id = 1
and date(timestamp) = date '2019-05-27'
order by timestamp desc
limit 1
) last_row;
Here is one way to get a result for multiple IDs:
select
minmax.generator_id,
(
select generated_value
from sun_electric.generator_meters gm
where gm.generator_id = minmax.generator_id
and gm.timestamp = minmax.max_ts
) -
(
select generated_value
from sun_electric.generator_meters gm
where gm.generator_id = minmax.generator_id
and gm.timestamp = minmax.min_ts
) as result
from
(
select generator_id, min(timestamp) as min_ts, max(timestamp) as max_ts
from sun_electric.generator_meters
where date(timestamp) = date '2019-05-27'
group by generator_id
) minmax
order by minmax.generator_id;
You can also move the subqueries to the from clause and join them, if you like this better. Yet another approach would be to use window functions, available as of MySQL 8.
This following script will return your expected results for the filtered ID and Date-
SELECT generator_id,CAST(timestamp AS DATE) ,
(
SELECT generated_value
FROM sun_electric.generator_meters B
WHERE timestamp = max(timestamp)
)
-
(
SELECT generated_value
FROM sun_electric.generator_meters B
WHERE timestamp = min(timestamp)
) AS Diff
FROM sun_electric.generator_meters
WHERE generator_id = 1
AND CAST(timestamp AS DATE) = '2019-05-27'
GROUP BY generator_id,CAST(timestamp AS DATE) ;
If you want the same result with GROUP BY ID and Date just remove the filter as below-
SELECT generator_id,CAST(timestamp AS DATE) ,
(
SELECT generated_value
FROM sun_electric.generator_meters B
WHERE timestamp = max(timestamp)
)
-
(
SELECT generated_value
FROM sun_electric.generator_meters B
WHERE timestamp = min(timestamp)
) AS Diff
FROM sun_electric.generator_meters
GROUP BY generator_id,CAST(timestamp AS DATE) ;

SQL query to extract the most recent part and anything that is 30 days older than that date.

I am very new to SQL and I need to write a query that selects data for a specific part. However, It should select only the part that is the most recent(given by date) and anything that is only 30 days prior to it. Please consider the table below:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
2 BBB 6/15/2015
3 AAA 6/11/2015
4 AAA 1/1/2008
I need a query that gives me:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
3 AAA 6/11/2015
I have tried:
select * from ( select * from sales_table where Part_NAME = 'AAA') where DATE BETWEEN (max(DATE) and (max(DATE)-30))
I have read some articles saying that I cannot use WHERE and functions like max() together and advised me to use group by or having but it didn't work for me as well. Thank you.
IF you want data from the last 30 days of the current day, you can do :
SELECT *
FROM sales_table
WHERE
[DATE] >= DATEADD(DAY, -30,GETDATE())
AND [DATE] <= GETDATE()
AND Part_NAME = 'AAA'
IF you want data from the last 30 days from the last date of sale of each Part_NAME (this will take the max recorded date of sale for each Part_NAME and get the last 30 days records of each one of them.)
SELECT *
FROM (
SELECT *,
MAX([DATE]) OVER(PARTITION BY Part_NAME ORDER BY PartID) AS RecentDate
FROM sales_table
) D
WHERE
[DATE] >= DATEADD(DAY, -30, RecentDate)
AND [DATE] <= RecentDate
AND Part_NAME = 'AAA'
You can accomplish by using datediff and getdate() and a subquery.
SELECT * FROM (
SELECT *,DATEDIFF(DD,[DATE],GETDATE()) AS DAYSBETWEEN FROM sales_table
) AS X
WHERE DAYSBETWEEN <= 30
If you want data from the last 30 days, it would be:
select st.*
from sales_table st join
(select top (1) st2.*
from sales_table st2
order by st2.date desc
) st2
on st2.part_name = st.part_name and
st.date >= dateadd(day, -30, cast(getdate() as date));

Calculate difference between first and last result

I have got a table with two columns. The first one ("val") is a integer, the second a timestamp ("ts").
Now I want to calculate the difference between the first and the last value of a given timespan.
SELECT MAX(val) - MIN(val) AS difference WHERE ts >= '2015-01-01 00:00:00' AND ts <= '2015-01-07 23:59:59'
This one is not sufficient, because in the course of time the values can exceed/undercut the first and the last value.
Example:
Day 1: 100
Day 2: 120
Day 3: 110
Day 4: 98
Day 5: 105
Day 6: 112
Day 7: 110
The difference is 110 (Day 7) minus 100 (Day 1) = 10. Not Max(val) = 120 minus Min(val) = 98 = 22
Thanks!
Join to a subquery that returns the first and last dates and join using those, and use some simple arithmetic to calculate the difference using sum():
select sum(val * case ts when ts1 then -1 else 1 end) diff
from data
join (select min(ts) ts1, max(ts) ts2
from data
where ts between ? and ?) x
on ts in (ts1, ts2)
See demo (demo schema has been simplified to isolate the essence of the solution).
One method is to use two subqueries, one that gets the first value and one that gets the last value:
SELECT ((SELECT val
FROM table t
WHERE ts >= '2015-01-01 00:00:00' AND ts <= '2015-01-07 23:59:59'
ORDER BY val DESC
LIMIT 1
) -
(SELECT val
FROM table t
WHERE ts >= '2015-01-01 00:00:00' AND ts <= '2015-01-07 23:59:59'
ORDER BY val ASC
LIMIT 1
)
) as difference

Mysql : Finding empty time blocks between two dates and times?

I wanted to find out user's availability from database table:
primary id | UserId | startdate | enddate
1 | 42 | 2014-05-18 09:00 | 2014-05-18 10:00
2 | 42 | 2014-05-18 11:00 | 2014-05-18 12:00
3 | 42 | 2014-05-18 14:00 | 2014-05-18 16:00
4 | 42 | 2014-05-18 18:00 | 2014-05-18 19:00
Let's consider above inserted data is user's busy time, I want to find out free time gap blocks from table between start time and end time.
BETWEEN 2014-05-18 11:00 AND 2014-05-18 19:00;
Let me add here schema of table for avoiding confusion:
Create Table availability (
pid int not null,
userId int not null,
StartDate datetime,
EndDate datetime
);
Insert Into availability values
(1, 42, '2013-10-18 09:00', '2013-10-18 10:00'),
(2, 42, '2013-10-18 11:00', '2013-10-18 12:00'),
(3, 42, '2013-10-18 14:00', '2013-11-18 16:00'),
(4, 42, '2013-10-18 18:00', '2013-11-18 19:00');
REQUIREMENT:
I wanted to find out free gap records like:
'2013-10-27 10:00' to '2013-10-28 11:00' - User is available for 1 hours and
'2013-10-27 12:00' to '2013-10-28 14:00' - User is available for 2 hours and
available start time is '2013-10-27 10:00' and '2013-10-27 12:00' respectively.
Here you go
SELECT t1.userId,
t1.enddate, MIN(t2.startdate),
MIN(TIMESTAMPDIFF(HOUR, t1.enddate, t2.startdate))
FROM user t1
JOIN user t2 ON t1.UserId=t2.UserId
AND t2.startdate > t1.enddate AND t2.pid > t1.pid
WHERE
t1.endDate >= '2013-10-18 09:00'
AND t2.startDate <= '2013-11-18 19:00'
GROUP BY t1.UserId, t1.endDate
http://sqlfiddle.com/#!2/50d693/1
Using your data, the easiest way is to list the hours when someone is free. The following gets a list of hours when someone is available:
select (StartTime + interval n.n hour) as FreeHour
from (select cast('2014-05-18 11:00' as datetime) as StartTime,
cast('2014-05-18 19:00' as datetime) as EndTime
) var join
(select 0 as n union all select 1 union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
) n
on StartTime + interval n.n hour <= EndTime
where not exists (select 1
from availability a
where StartTime + interval n.n hour < a.EndDate and
StartTime + interval n.n hour >= a.StartDate
);
EDIT:
The general solution to your problem requires denormalizing the data. The basic query is:
select thedate, sum(isstart) as isstart, #cum := #cum + sum(isstart) as cum
from ((select StartDate as thedate, 1 as isstart
from availability a
where userid = 42
) union all
(select EndDate as thedate, -1 as isstart
from availability a
where userid = 42
) union all
(select cast('2014-05-18 11:00' as datetime), 0 as isstart
) union all
(select cast('2014-05-18 19:00' as datetime), 0 as isstart
)
) t
group by thedate cross join
(select #cum := 0) var
order by thedate
You then just choose the values where cum = 0. The challenge is getting the next date from this list. In MySQL that is a real pain, because you cannot use a CTE or view or window function, so you have to repeat the query. This is why I think the first approach is probably better for your situation.
The core query can be this. You can dress it up as you like, but I'd handle all that stuff in the presentation layer...
SELECT a.enddate 'Available From'
, MIN(b.startdate) 'To'
FROM user a
JOIN user b
ON b.userid = a.user
AND b.startdate > a.enddate
GROUP
BY a.enddate
HAVING a.enddate < MIN(b.startdate)
For times outside the 'booked' range, you have to extend this a little with a UNION, or again handle the logic at the application level