Update with SUM and LIMIT, rolling SUM - mysql

I have 2 tables, SVISE and OVERW
Inside OVERW I have some scores with person ids and the date of that score.
E.g
p_id degrees mo_date
5 10.2 2013-10-09
5 9.85 2013-03-10
8 14.75 2013-04-25
8 11.00 2013-02-22
5 5.45 2013-08-11
5 6.2 2013-06-10
SVISE.ofh field must be updated with the sum of the last three records
(for a specific person, ordered by date descending), so for person with id 5, the sum would result from the rows
5 10.2 2013-10-09
5 5.45 2013-08-11
5 6.2 2013-06-10
sum=21.85.
Desired final result on SVISE, based on the values above:
HID OFH START
5 21.85 October, 16 2013 ##(10.2 + 5.45 + 6.2)
5 21.5 September, 07 2013 ##(5.45 + 6.2 + 9.85)
5 0 March, 05 2013 ##(no rows)
8 25.75 October, 14 2013 ##(14.75 + 11)
3 0 October, 14 2013 ##(no rows)
5 0 March, 05 2012 ##(no rows)
OFHwas 0 initially
I can get the total sum for a specific person, but I can't use limit to get the last 3 rows. It gets ignored.
This is the query I use to retrieve the sum of all degrees per person for a given date:
UPDATE SVISE SV
SET
SV.ofh=(SELECT sum(degrees) FROM OVERW WHERE p_id =SV.hid
AND date(mo_date)<date(SV.start)
AND year(mo_date)=year(SV.start))
I cannot just use limit with sum:
UPDATE SVISE SV
SET
SV.ofh=(SELECT sum(degrees) FROM OVERW WHERE p_id =SV.hid
AND date(mo_date)<date(SV.start)
AND year(mo_date)=year(SV.start)
ORDER BY mo_date DESC
LIMIT 3)
This does not work.
I have tried with multi-table updates and with nested queries to achieve this.
Every scenario has known limitations that block me from accomplishing the desired result.
Nested queries cant see the parent table. Unknown column 'SV.hid'in 'where clause'
Multi-table update cant be use with limit. Incorrect usage of UPDATE and LIMIT
Any solution will do. There is no need to do it in a single query. If anyone wants to try even with an intermediate table.
An SQL fiddle is also available.
Thanks in advance for your help.
--Update--
Here is the solution from Akash: http://sqlfiddle.com/#!2/4cf1a/1

This should work,
UPDATED to have a join on svice
UPDATE
svice SV
JOIN (
SELECT
hid,
start,
sum(degrees) as degrees
FROM
(
SELECT
*,
IF(#prev_row <> unix_timestamp(start)+P_ID, #row_number:=0,NULL),
#prev_row:=unix_timestamp(start)+P_ID,
#row_number:=#row_number+1 as row_number
FROM
(
SELECT
mo_date,
p_id,
hid,
start,
degrees
FROM
OVERW
JOIN svice sv ON ( p_id = hid
AND date(mo_date)<date(SV.start)
AND year(mo_date)=year(SV.start) )
ORDER BY
hid,
start,
mo_date desc
) sub_query1
JOIN ( select #row_number:=0, #prev_row:=0 ) sub_query2
) sub_query
where
row_number <= 3
GROUP BY
hid,
start
) sub_query ON ( sub_query.hid = sv.hid AND sub_query.start = sv.start )
SET
SV.ofh = sub_query.degrees
Note: Check this with your updated data, the test data provided could not yield the results you expected due to the date conditions

Try
UPDATE svice SV
JOIN (SELECT SUM(degrees)sumdeg,p_id FROM(SELECT DISTINCT degrees,p_id FROM OVERW,svice WHERE OVERW.p_id IN (SELECT svice.hid FROM svice)
AND date(mo_date)<date(svice.start)
AND year(mo_date)=year(svice.start)ORDER BY mo_date DESC )deg group by p_id)bbc
ON bbc.p_id=SV.hid
SET
SV.ofh=bbc.sumdeg where p_id =SV.hid
http://sqlfiddle.com/#!2/95b42/42
Getting closer,now it "only" needs a limit in GROUP BY.

Two assumptions:
You can figure out how to turn this into an update, and
A PK exists on (id,mo_date)
Then you can do this -
SELECT p_id
, SUM(degrees) ttl
FROM
( SELECT x.*
FROM overw x
JOIN overw y
ON y.p_id = x.p_id
AND y.mo_date >= x.mo_date
GROUP
BY p_id
, mo_date HAVING COUNT(*) <= 3
) a
GROUP
BY p_id;

Maybe I'm slow, but let's ignore svice for now.
Can you show the correct result and the working for each row below...
+------+---------+------------+--------+
| p_id | degrees | mo_date | result |
+------+---------+------------+--------+
| 5 | 6.20 | 2013-06-10 | ? |
| 5 | 5.45 | 2013-08-11 | ? |
| 5 | 10.20 | 2013-10-09 | 21.85 | <- = 10.2+5.45+6.2 = 21.85
| 8 | 14.75 | 2013-04-25 | ? |
| 5 | 9.85 | 2013-03-10 | ? |
| 8 | 11.00 | 2013-02-22 | ? |
+------+---------+------------+--------+

Related

SQL query - selecting first and last record of each day in a month

Mysql 5.7.30: I have two tables:
projects: projectID, timeNeeded
registrations: registrationID, projectID, startDateTime
What I want to achieve:
For the current month (based on startDateTime) I want to know "timeNeeded" by selecting the first and last record for each day in the month. If there's only 1 record for a day, it should still count it twice.
E.g if there's 4 registrations on one day, I only want to include the first and last of those 4.
I'm a little unsure how to get started here, i guess there's multiple ways to achieve this. Speed isn't important, as long as it's better than my first idea; using multiple queries and PHP to process it.
Sample data and wanted result:
Project table:
project1 50
project2 20
project3 30
Registation table: (hour:minute hidden)
reg1 project1 2020-07-01
reg2 project1 2020-07-01
reg3 project3 2020-07-02
reg4 project3 2020-07-02
reg5 project2 2020-07-02
reg6 project2 2020-07-02
reg7 project3 2020-07-03
reg8 project1 2020-07-04
reg9 project3 2020-07-05
reg10 project2 2020-07-05
Result (projects.timeNeeded for first and last of each day):
reg1 50
reg2 50
reg3 30
reg6 20
reg7 30
reg7 30
reg8 50
reg8 50
reg9 30
reg10 20
The tricky part of this requirement is the double rows for the dates that have only 1 registration, this is why I use UNION ALL.
Aggregation is needed to get the first and last startDateTime of each day and finally joins:
select r.registrationID, p.timeNeeded
from (
select registrationID, projectID, startDateTime
from Registration
union all
select max(registrationID), max(projectID), max(startDateTime)
from Registration
group by date(startDateTime)
having count(*) = 1
) r
inner join (
select date(startDateTime) date,
min(startDateTime) min_date,
max(startDateTime) max_date
from Registration
where date_format(startDateTime, "%Y-%m") = date_format(current_date, "%Y-%m")
group by date
) t on r.startDateTime in (t.min_date, t.max_date)
inner join Project p on p.projectID = r.projectID
order by r.startDateTime
See the demo.
Results:
| registrationID | timeNeeded |
| -------------- | ---------- |
| reg1 | 50 |
| reg2 | 50 |
| reg3 | 30 |
| reg6 | 20 |
| reg7 | 30 |
| reg7 | 30 |
| reg8 | 50 |
| reg8 | 50 |
| reg9 | 30 |
| reg10 | 20 |
I would approach this by using union all, once for the first record on each day and once for the last:
select r.*, p.timeneeded
from Registration r join
Project p
on r.projectid = p.projectid
where extract(year_month from r.startDateTime) = extract(year_month from now()) and
r.registrationID = (select r2.registrationID
from Registration r2
where date(r2.startDateTime) = date(r.startDatetime)
order by r2.registrationID
limit 1
)
union all
select r.*, p.timeneeded
from Registration r join
Project p
on r.projectid = p.projectid
where extract(year_month from r.startDateTime) = extract(year_month from now()) and
r.registrationID = (select r2.registrationID
from Registration r2
where date(r2.startDateTime) = date(r.startDatetime)
order by r2.registrationID desc
limit 1
)
order by registrationID;
Note: Your dates are all the same. The name of the column suggests that there might be a time component, but your question doesn't have it. So this uses the registration id to determine the first and last on each day.
Here is a db<>fiddle.
Since your table is missing the headers, am writing a bit abstract.
select * from registration sort by timestamp_col desc limit 1;
and
select * from registration sort by timestamp_col limit 1;
These two queries should give you the first and last registrations. In case you have only one registration, then both queries will return you the same record. This will meet your needs.
I have ignored the join with the Project table, assuming you know how to join two tables.

How to display data at the end of the month

How do you display the latest data for a particular month?
For example I have data like this
date data
2003-04-18 | 12
2003-04-25 | 14
2003-05-16 | 51
2003-05-23 | 21
2003-05-30 | 22
2003-06-06 | 11
2003-06-27 | 14
I want to display data
date data
2003-04-25 | 14
2003-05-30 | 22
2003-06-27 | 14
What kind of example query is used?
i try this query, but showing all data
SELECT tgl_hargaemas, harga FROM tbl_hargaemas
WHERE tgl_hargaemas >= '2003-01-03' - INTERVAL DAYOFMONTH('2018-01-03') MONTH
Use a simple GROUP BY sub query to find the max date for each year-month followed by an INNER JOIN:
SELECT t.*
FROM (
SELECT MAX(date) AS maxdate
FROM t
GROUP BY YEAR(date), MONTH(date)
) AS g
INNER JOIN t ON g.maxdate = t.date
Demo on DB<>Fiddle

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);

How to fetch average rows per day?

I have a table
id | created
---------------
1 | 2014-07-01
2 | 2014-07-01
3 | 2014-07-01
4 | 2014-07-01
5 | 2014-07-02
6 | 2014-07-03
7 | 2014-07-04
8 | 2014-07-05
9 | 2014-07-05
10 | 2014-07-05
and I want to fetch the average of rows per day. So if it's
2014-07-01: 4 rows
2014-07-02: 1 row
2014-07-03: 1 row
2014-07-04: 1 row
2014-07-05: 3 rows
The average would be
sum 4 + 1 + 1 + 1 + 3 = 10 items
10 items / 5 days = 2 items/day
My SQL query is
SELECT AVG(COUNT(*))
FROM `mytable`
GROUP BY `created`
But I get this error
#1111 - Invalid use of group function
How do I have to modify my query in order to get a correct result?
You can do this without subquery also.It is much fatser.
SELECT COUNT(*)/COUNT(DISTINCT `created`) FROM `mytable`;
Here is the example link: http://sqlfiddle.com/#!2/2c284df/2
You need to put it in a subquery.
SELECT AVG(my_count) FROM (
SELECT COUNT(*) as my_count
FROM
your_table
GROUP BY created
) sq
see it working live in an sqlfiddle
Have a sub query to get the count per day, and then use that to get the average:-
SELECT AVG(day_count)
FROM
(
SELECT COUNT(*) AS day_count
FROM `mytable`
GROUP BY `created`
) sub0

Fetch Unit consumption date-wise

I am struggling in to get result from mysql in the following way. I have 10 records in mysql db table having date and unit fields. I need to get used units on every date.
Table structure as follows, adding today unit with past previous unit in every record:
Date Units
---------- ---------
10/10/2012 101
11/10/2012 111
12/10/2012 121
13/10/2012 140
14/10/2012 150
15/10/2012 155
16/10/2012 170
17/10/2012 180
18/10/2012 185
19/10/2012 200
Desired output will be :
Date Units
---------- ---------
10/10/2012 101
11/10/2012 10
12/10/2012 10
13/10/2012 19
14/10/2012 10
15/10/2012 5
16/10/2012 15
17/10/2012 10
18/10/2012 5
19/10/2012 15
Any help will be appreciated. Thanks
There's a couple of ways to get the resultset. If you can live with an extra column in the resultset, and the order of the columns, then something like this is a workable approach.
using user variables
SELECT d.Date
, IF(#prev_units IS NULL
,#diff := 0
,#diff := d.units - #prev_units
) AS `Units_used`
, #prev_units := d.units AS `Units`
FROM ( SELECT #prev_units := NULL ) i
JOIN (
SELECT t.Date, t.Units
FROM mytable t
ORDER BY t.Date, t.Units
) d
This returns the specified resultset, but it includes the Units column as well. It's possible to have that column filtered out, but it's more expensive, because of the way MySQL processes an inline view (MySQL calls it a "derived table")
To remove that extra column, you can wrap that in another query...
SELECT f.Date
, f.Units_used
FROM (
query from above goes here
) f
ORDER BY f.Date
but again, removing that column comes with the extra cost of materializing that result set a second time.
using a semi-join
If you are guaranteed to have a single row for each Date value, either stored as a DATE, or as a DATETIME with the timecomponent set to a constant, such as midnight, and no gaps in the Date value, and Date is defined as DATE or DATETIME datatype, then another query that will return the specifid result set:
SELECT t.Date
, t.Units - s.Units AS Units_Used
FROM mytable t
LEFT
JOIN mytable s
ON s.Date = t.Date + INTERVAL -1 DAY
ORDER BY t.Date
If there's a missing Date value (a gap) such that there is no matching previous row, then Units_used will have a NULL value.
using a correlated subquery
If you don't have a guarantee of no "missing dates", but you have a guarantee that there is no more than one row for a particular Date, then another approach (usually more expensive in terms of performance) is to use a correlated subquery:
SELECT t.Date
, ( t.Units - (SELECT s.Units
FROM mytable s
WHERE s.Date < t.Date
ORDER BY s.Date DESC
LIMIT 1)
) AS Units_used
FROM mytable t
ORDER BY t.Date, t.Units
spencer7593's solution will be faster, but you can also do something like this...
SELECT * FROM rolling;
+----+-------+
| id | units |
+----+-------+
| 1 | 101 |
| 2 | 111 |
| 3 | 121 |
| 4 | 140 |
| 5 | 150 |
| 6 | 155 |
| 7 | 170 |
| 8 | 180 |
| 9 | 185 |
| 10 | 200 |
+----+-------+
SELECT a.id,COALESCE(a.units - b.units,a.units) units
FROM
( SELECT x.*
, COUNT(*) rank
FROM rolling x
JOIN rolling y
ON y.id <= x.id
GROUP
BY x.id
) a
LEFT
JOIN
( SELECT x.*
, COUNT(*) rank
FROM rolling x
JOIN rolling y
ON y.id <= x.id
GROUP
BY x.id
) b
ON b.rank= a.rank -1;
+----+-------+
| id | units |
+----+-------+
| 1 | 101 |
| 2 | 10 |
| 3 | 10 |
| 4 | 19 |
| 5 | 10 |
| 6 | 5 |
| 7 | 15 |
| 8 | 10 |
| 9 | 5 |
| 10 | 15 |
+----+-------+
This should give the desired result. I don't know how your table is called so I named it "tbltest".
Naming a table date is generally a bad idea as it also refers to other things (functions, data types,...) so I renamed it "fdate". Using uppercase characters in field names or tablenames is also a bad idea as it makes your statements less database independent (some databases are case sensitive and some are not).
SELECT
A.fdate,
A.units - coalesce(B.units, 0) AS units
FROM
tbltest A left join tbltest B ON A.fdate = B.fdate + INTERVAL 1 DAY