Earliest time of daily maximum values - mysql

I have a table that logs weather data variables by datetime like this:
|------------------|------------| ----
| LogDateTime | Temp | ...
+------------------|------------| ----
| 2020-01-01 00:00 | 20.1 | ...
| 2020-01-01 00:05 | 20.1 | ...
| 2020-01-01 00:10 | 19.9 | ...
| 2020-01-01 00:15 | 19.8 | ...
---------------------------------------
From that table I want to return the earliest time of the maximum temperature for each day like this (just the time portion of the datetime value):
|------------|----------------------
| LogDate | LogTime| MaxTemp
+---------------------|--------------
| 2020-01-01 | 14:00 | 24.5
| 2020-01-02 | 15:12 | 23.2
| 2020-01-03 | 10:12 | 25.1
| 2020-01-04 | 12:14 | 28.8
--------------------------------
The query I have to return this so far is the below, but it returns the earliest temperature for each day instead of the earliest occurrence of the maximum temperature for each day
SELECT TIME(a.LogDateTime), a.Temp
FROM Monthly a
INNER JOIN (
SELECT TIME(LogDateTime), LogDateTime, MAX(Temp) Temp
FROM Monthly
GROUP BY LogDateTime
) b ON a.LogDateTime = b.LogDateTime AND a.Temp= b.Temp
GROUP BY DATE(a.LogDateTime)
I then want to use that query to update a table of one row per day that summarises the minimum and maximum values with a query something like this but update the time rather than the actual maximum temperature:
UPDATE Dayfile AS d
JOIN (
SELECT DATE(LogDateTime) AS date, MAX(Temp) AS Temps
FROM Monthly
GROUP BY date
) AS m ON DATE(d.LogDate) = m.date
SET d.MaxTemp = m.Temps

Your version of MariaDB supports window functions, so use ROW_NUMBER():
select LogDateTime, Temp
from (
select *,
row_number() over (partition by date(LogDateTime) order by Temp desc, LogDateTime) rn
from Monthly
) t
where t.rn = 1
See a simplified demo.
Use it to update Dayfile like this:
update Dayfile d
inner join (
select LogDateTime, Temp
from (
select *,
row_number() over (partition by date(LogDateTime) order by Temp desc, LogDateTime) rn
from Monthly
) t
where t.rn = 1
) m on date(d.LogDate) = m.date
set d.MaxTemp = m.Temp

Related

MySQL get previous day data

I have the following table
Date | amount 1 |
-----------|-------------|
2020-01-01 | 100 |
2020-01-02 | 120 |
2020-01-03 | 150 |
What I try to get is writing the day before data on the following day
Date | amount 1 | amount 2 |
-----------|-------------|----------|
2020-01-01 | 100 | 0 |
2020-01-02 | 120 | 100 |
2020-01-03 | 150 | 120 |
I can get yesterday but don't know how to do it for all rows.
Thanks,
You can use next approach.
select
test.date1,
test.amount1,
ifnull(yestarday_test.amount1, 0) as amount2
from test
left join test yestarday_test on
date_sub(yestarday_test.date1, interval -1 day ) = test.date1
order by test.date1 asc
;
In this query we use join same table to itself by date with 1 day shift.
DB Fiddle
Use lag():
select date, amount,
lag(amount, 1, 0) over (order by date) as amount_prev
from t;
In MySQL < 8.0, where window functions are not available, one option is a correlated subquery:
select
date,
amount,
(
select t1.amount
from mytable t1
where t1.date < t.date
order by t1.date desc limit 1
) prev_amount
from mytable t

Create a row for every day in a date range?

I have a table like this:
+----+---------+------------+
| id | price | date |
+----+---------+------------+
| 1 | 340 | 2018-09-02 |
| 2 | 325 | 2018-09-05 |
| 3 | 358 | 2018-09-08 |
+----+---------+------------+
And I need to make a view which has a row for every day. Something like this:
+----+---------+------------+
| id | price | date |
+----+---------+------------+
| 1 | 340 | 2018-09-02 |
| 1 | 340 | 2018-09-03 |
| 1 | 340 | 2018-09-04 |
| 2 | 325 | 2018-09-05 |
| 2 | 325 | 2018-09-06 |
| 2 | 325 | 2018-09-07 |
| 3 | 358 | 2018-09-08 |
+----+---------+------------+
I can do that using PHP with a loop (foreach) and making a temp variable which holds the previous price til there is a new date.
But I need to make a view ... So I should do that using pure-SQL .. Any idea how can I do that?
You could use a recursive CTE to generate the records in the "gaps". To avoid that an infinite gap after the last date is "filled", first get the maximum date in the source data and make sure not to bypass that date in the recursion.
I have called your table tbl:
with recursive cte as (
select id,
price,
date,
(select max(date) date from tbl) mx
from tbl
union all
select cte.id,
cte.price,
date_add(cte.date, interval 1 day),
cte.mx
from cte
left join tbl
on tbl.date = date_add(cte.date, interval 1 day)
where tbl.id is null
and cte.date <> cte.mx
)
select id,
price,
date
from cte
order by 3;
demo with mysql 8
Here is an approach which should work without analytic functions. This answer uses a calendar table join approach. The first CTE below is the base table on which the rest of the query is based. We use a correlated subquery to find the most recent date earlier than the current date in the CTE which has a non NULL price. This is the basis for finding out what the id and price values should be for those dates coming in from the calendar table which do not appear in the original data set.
WITH cte AS (
SELECT cal.date, t.price, t.id
FROM
(
SELECT '2018-09-02' AS date UNION ALL
SELECT '2018-09-03' UNION ALL
SELECT '2018-09-04' UNION ALL
SELECT '2018-09-05' UNION ALL
SELECT '2018-09-06' UNION ALL
SELECT '2018-09-07' UNION ALL
SELECT '2018-09-08'
) cal
LEFT JOIN yourTable t
ON cal.date = t.date
),
cte2 AS (
SELECT
t1.date,
t1.price,
t1.id,
(SELECT MAX(t2.date) FROM cte t2
WHERE t2.date <= t1.date AND t2.price IS NOT NULL) AS nearest_date
FROM cte t1
)
SELECT
(SELECT t2.id FROM yourTable t2 WHERE t2.date = t1.nearest_date) id,
(SELECT t2.price FROM yourTable t2 WHERE t2.date = t1.nearest_date) price,
t1.date
FROM cte2 t1
ORDER BY
t1.date;
Demo
Note: To make this work on MySQL versions earlier than 8+, you would need to inline the CTEs above. It would result in verbose code, but, it should still work.
Since you are using MariaDB, it is rather trivial:
MariaDB [test]> SELECT '2019-01-01' + INTERVAL seq-1 DAY FROM seq_1_to_31;
+-----------------------------------+
| '2019-01-01' + INTERVAL seq-1 DAY |
+-----------------------------------+
| 2019-01-01 |
| 2019-01-02 |
| 2019-01-03 |
| 2019-01-04 |
| 2019-01-05 |
| 2019-01-06 |
(etc)
There are variations on this wherein you generate a large range of dates, but then use a WHERE to chop to what you need. And use LEFT JOIN with the sequence 'derived table' on the 'left'.
Use something like the above as a derived table in your query.

Select last inserted value of each month for every year from DATETIME

I got a DATETIME to store when the values where introduced, like this example shows:
CREATE TABLE IF NOT EXISTS salary (
change_id INT(11) NOT NULL AUTO_INCREMENT,
emp_salary FLOAT(8,2),
change_date DATETIME,
PRIMARY KEY (change_id)
);
I gonna fill the example like this:
+-----------+------------+---------------------+
| change_id | emp_salary | change_date |
+-----------+------------+---------------------+
| 1 | 200.00 | 2018-06-18 13:17:17 |
| 2 | 700.00 | 2018-06-25 15:20:30 |
| 3 | 300.00 | 2018-07-02 12:17:17 |
+-----------+------------+---------------------+
I want to get the last inserted value of each month for every year.
So for the example I made, this should be the output of the Select:
+-----------+------------+---------------------+
| change_id | emp_salary | change_date |
+-----------+------------+---------------------+
| 2 | 700.00 | 2018-06-25 15:20:30 |
| 3 | 300.00 | 2018-07-02 12:17:17 |
+-----------+------------+---------------------+
1 won't appear because is an outdated version of 2
You could use a self join to pick group wise maximum row, In inner query select max of change_date by grouping your data month and year wise
select t.*
from your_table t
join (
select max(change_date) max_change_date
from your_table
group by date_format(change_date, '%Y-%m')
) t1
on t.change_date = t1.max_change_date
Demo
If you could use Mysql 8 which has support for window functions you could use common table expression and rank() function to pick row with highest change_date for each year and month
with cte as(
select *,
rank() over (partition by date_format(change_date, '%Y-%m') order by change_date desc ) rnk
from your_table
)
select * from cte where rnk = 1;
Demo
The below query should work for you.
It uses group by on month and year to find max record for each month and year.
SELECT s1.*
FROM salary s1
INNER JOIN (
SELECT MAX(change_date) maxDate
FROM salary
GROUP BY MONTH(change_date), YEAR(change_date)
) s2 ON s2.maxDate = s1.change_date;
Fiddle link : http://sqlfiddle.com/#!9/1bc20b/15

Select rows from MySQL and grouping use MAX and MIN

I have the following table called 'ArchiveTable':
+---------+-----------------+---------+-----------------+--------------+------------------+
| maxtemp | maxtemptime | mintemp | mintemptime | minwindchill | minwindchilltime |
+---------+-----------------+---------+-----------------+--------------+------------------+
| 27.9 | 3/17/2015 16:55 | 25.8 | 3/17/2015 19:00 | 25.8 | 3/17/2015 19:00 |
+---------+-----------------+---------+-----------------+--------------+------------------+
| 25.7 | 3/17/2015 19:05 | 19.3 | 3/18/2015 9:05 | 19.3 | 3/18/2015 9:05 |
+---------+-----------------+---------+-----------------+--------------+------------------+
| 23.1 | 3/18/2015 19:05 | 18.7 | 3/19/2015 6:30 | 18.7 | 3/19/2015 6:30 |
+---------+-----------------+---------+-----------------+--------------+------------------+
I have to select the maximum value of 'maxtemp' and its corresponding 'maxtemptime' date, minimum value of 'mintemp' and its corresponding date, and minimum value of 'minwindchill' and its corresponding date.
I know how to obtain the max and min values with the MAX() and MIN() functions, but I cannot associate these values to the corresponding date.
If you could take the values on separate rows, then you could do something like this:
(select a.* from archivetable order by maxtemp limit 1) union
(select a.* from archivetable order by maxtemp desc limit 1) union
. . .
Otherwise, if you can do something like this:
select atmint.mintemp, atmint.mintempdate,
atmaxt.maxtemp, atmaxt.maxtempdate,
atminwc.minwindchill, atminwc.minwindchilldate
from (select min(mintemp) as mintemp, max(maxtemp) as maxtemp, min(minwindchill) as minwindchill
from archivetable
) a join
archivetable atmint
on atmint.mintemp = a.mintemp join
archivetable atmaxt
on atmaxt.maxtemp = a.maxtemp join
archivetable atminwc
on atminwc.minwindchill = a.minwindchill
limit 1;
The limit 1 is because multiple rows might have the same values. If so, you can arbitrarily choose one of them, based on how your question is phrased.
See this MySQL Handling of GROUP BY
If I understood you correctly you should do something like this
SELECT field1, field2, fieldN, COUNT(field1) AS alias FROM table
GROUP BY field1
HAVING maxtemp = MAX(maxtemp); -- I think this is not correct
Although I'm not 100% sure about that solution you could try this as well
SELECT field1, field2, fieldN, COUNT(field1) AS alias FROM table
GROUP BY field1
HAVING maxtemp = (SELECT MAX(maxtemp) FROM table);

MySql Get sum by date range

Here is some table for storing advertising campaign budgets history:
campaign_budgets_history
id_campaign budget date
1 10 2013-01-01
1 15 2013-01-03
1 10 2013-01-05
If there are no data for some date, it would be equal to the last set budget.
How can I count the sum of budgets by date range, for example from '2013-01-02' to '2013-01-06'. The result must be $60, because of the budget for '2013-01-02' would be equal to '2013-01-01', and budget for '2013-01-04' would be equal to '2013-01-03'.
Is there are any way to do it via SQL?
Here is a query for you. It uses user variables to denote the ends of the query range, but in the final version you'll likely rather use parameter placeholders instead. Note that #end is the first day after the range you query, i.e. it's the exclusive end of the range.
SET #begin = '2013-01-02';
SET #end = '2013-01-07';
SELECT
SUM(DATEDIFF(IF(CAST(c.end AS date) > CAST(#end AS date),
CAST(#end AS date),
CAST(c.end AS date)
),
IF(c.begin < CAST(#begin AS date),
CAST(#begin AS date),
c.begin
)
) * c.budget
) AS overall_budget
FROM
(SELECT a.id_campaign,
a.date begin,
MIN(IFNULL(b.date, CAST(#end AS date))) end,
a.budget
FROM campaign_budgets_history a
LEFT JOIN campaign_budgets_history b
ON a.id_campaign = b.id_campaign AND a.date < b.date
WHERE a.date < CAST(#end AS date)
GROUP BY a.id_campaign, a.date
HAVING end > CAST(#begin AS date)
) c;
Tested on SQL Fiddle. Not sure why all the casts seem necessary, perhaps there is a way to avoid some of them. But the above appears to work, and some versions with less casts did not.
The idea is that the subquery creates a table of ranges, each denoting the dates where a given budget was in effect. You might have to adjust the beginning of the first range, to match the beginning of your query range. Then you simply subtract the dates to obtain the number of days for each, and multiply that number by the daily budget.
This example uses a calendar (utility) table...
SELECT * FROM calendar WHERE dt BETWEEN '2012-12-27' AND '2013-01-12';
+------------+
| dt |
+------------+
| 2012-12-27 |
| 2012-12-28 |
| 2012-12-29 |
| 2012-12-30 |
| 2012-12-31 |
| 2013-01-01 |
| 2013-01-02 |
| 2013-01-03 |
| 2013-01-04 |
| 2013-01-05 |
| 2013-01-06 |
| 2013-01-07 |
| 2013-01-08 |
| 2013-01-09 |
| 2013-01-10 |
| 2013-01-11 |
| 2013-01-12 |
+------------+
SELECT SUM(budget) total
FROM campaign_budgets_history a
JOIN
( SELECT MAX(y.date) max_date
FROM calendar x
JOIN campaign_budgets_history y
ON y.date <= x.dt
WHERE x.dt BETWEEN '2013-01-02' AND '2013-01-06'
GROUP
BY x.dt
) b
ON b.max_date = a.date;