SQL: Increment for every GROUP BY - mysql

I'm wanting to return the total number of times an exerciseID is listed, but filter it so each exerciseID may only be increment once per a date. For this reason I believe I cannot do a group by date.
id | exerciseid | date
1 | 105 | 2014-01-01 00:00:00
2 | 105 | 2014-02-01 00:00:00
3 | 105 | 2014-03-11 00:00:00
4 | 105 | 2014-03-11 00:00:00
5 | 105 | 2014-03-11 00:00:00
6 | 127 | 2014-01-01 00:00:00
7 | 127 | 2014-02-02 00:00:00
8 | 127 | 2014-02-02 00:00:00
// 105 = 5 total rows but 3 unique
// 127 = 3 total rows but 2 unique
$db->query("SELECT exerciseid as id, sum(1) as total
FROM `users exercises` as ue
WHERE userid = $userid
GROUP BY exerciseid
ORDER BY date DESC");
Current Output:
Array
(
[id] => 105
[date] => 2014-05-06
[total] => 5
)
Array
(
[id] => 127
[date] => 2014-05-06
[total] => 3
)
As you can see it's not merging the rows where the date and exerciseid are the same.
Expected Result:
Array
(
[id] => 105
[date] => 2014-05-06
[total] => 3
)
Array
(
[id] => 127
[date] => 2014-05-06
[total] => 2
)

for V2.0 question:
select
exerciseid
, count(distinct date) as exercise_count
from user_exercises
group by
exerciseid
;
| EXERCISEID | EXERCISE_COUNT |
|------------|----------------|
| 54 | 1 |
| 85 | 3 |
| 420 | 2 |
see this sqlfiddle

If you want count how many group you have on group by :
$db->query("SELECT e.id as id, e.name, count(id) as total, ue.date
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
WHERE ue.`userid` = $userid
GROUP BY id ASC
ORDER BY total DESC");
else if you want take previous total for addition, create a procedure like this (I think there are errors in my procedure)
CREATE PROCEDURE name
DECLARE
record your_table%ROWTYPE;
nb int DEFAULT 0;
BEGIN
FOR record IN SELECT e.id as id, e.name as name, count(id) as nbid, ue.date as date
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
WHERE ue.`userid` = $userid
GROUP BY id ASC
ORDER BY total DESC
LOOP
set nb := nb + record.nbid;
SELECT record.id,record.name,nb,date;
END LOOP;
END
regards
Dragondark De Lonlindil

I think you are seeking a running total.
| USERID | DATE | NAME | RUNNINGSUM |
|--------|------------------------------|---------------------|------------|
| 1 | May, 10 2014 00:00:00+0000 | football | 1 |
| 1 | June, 10 2014 00:00:00+0000 | football | 2 |
| 1 | July, 10 2014 00:00:00+0000 | football | 3 |
| 1 | July, 10 2014 00:00:00+0000 | football | 4 |
| 1 | May, 10 2014 00:00:00+0000 | Machine Bench Press | 5 |
| 1 | June, 10 2014 00:00:00+0000 | Machine Bench Press | 6 |
| 1 | March, 10 2014 00:00:00+0000 | salsa | 7 |
MySQL lacks functions like row_number() that make this simple, but here is an approach that achieves the equivalent of row_number() partitioned by userid.
SELECT
userid
, date
, name
, RunningSum
FROM(
SELECT #row_num := IF(#prev_user = ue.userid, #row_num + 1 ,1) AS RunningSum
, ue.userid
, ue.date
, e.name
, #prev_user := ue.userid
FROM user_exercises ue
INNER JOIN exercises e ON ue.exerciseid = e.id
CROSS JOIN (SELECT #row_num :=1, #prev_user :='') vars
ORDER BY
ue.userid
, ue.date
, ue.exerciseid
) x
ORDER BY
userid
, RunningSum
see this sqlfiddle

Related

How to select last entries for each groups?

I have the following table:
ID | team1 | team2 | Date
-----------------------------
1 | 36 | 25 | 2019-01-05
2 | 25 | 39 | 2019-01-07
3 | 36 | 39 | 2019-01-09
4 | 36 | 11 | 2019-01-10
5 | 11 | 25 | 2019-01-11
6 | 25 | 36 | 2019-01-12
How to get last 2 entries for team 25 and 36. Good result is:
ID | team1 | team2 | Date
-----------------------------
4 | 36 | 11 | 2019-01-10 > 36
5 | 11 | 25 | 2019-01-11 > 25
6 | 25 | 36 | 2019-01-12 > 25 & 36
25 and 36 is just for example. We can have a long list of teams, as well as a very large list of entries. If the search would be carried out on one column, for example team1 then the query would look like this:
SELECT * FROM (
SELECT
ID, team1, team2, `Date`,
CASE WHEN #id != team1 THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#id := team1
FROM matches
JOIN (SELECT #rownum := 0, #id := NULL) r
WHERE team1 IN(25, 36)
OREDER BY team1, `Date` DESC
) WHERE rank <= 2
You can use a LEFT OUTER JOIN to join the table against itself for later games, and discard any rows where there is a layter game:-
SELECT a.*
FROM matches a
LEFT OUTER JOIN matches b
ON a.team1 = b.team1
AND a.`date` < b.`date`
WHERE b.id IS NULL
AND a.team1 IN (25, 36)
If you apply your query to the UNION of the 2 columns and join to the table you will get the result that you want:
SELECT DISTINCT m.*
FROM matches m INNER JOIN (
SELECT * FROM (
SELECT team, `Date`,
CASE WHEN #id != team THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#id := team
FROM (
SELECT team1 team, date FROM matches
UNION ALL
SELECT team2, date FROM matches
) t
JOIN (SELECT #rownum := 0, #id := NULL) r
WHERE team IN (25, 36)
ORDER BY team, `Date` DESC
) t
WHERE rank <= 2
) t ON t.`Date` = m.`Date` AND t.team IN (m.team1, m.team2)
See the demo.
Results:
| ID | team1 | team2 | Date |
| --- | ----- | ----- | ------------------- |
| 4 | 36 | 11 | 2019-01-10 00:00:00 |
| 5 | 11 | 25 | 2019-01-11 00:00:00 |
| 6 | 25 | 36 | 2019-01-12 00:00:00 |

Get all streaks of consecutive dates from a user score table

Table score_streak is used to store a user's daily scores and is defined as follows.
CREATE TABLE IF NOT EXISTS score_streak(
create_date DATE NOT NULL,
score INT(11),
PRIMARY KEY (create_date)
);
insert into score_streak (create_date, score) values
(DATE('2017-04-01'), 11) ,
(DATE('2017-04-02'), 8) ,
(DATE('2017-04-03'), 9) ,
(DATE('2017-04-06'), 14) ,
(DATE('2017-04-07'), 15) ,
(DATE('2017-04-08'), 13) ,
(DATE('2017-04-12'), 20) ,
(DATE('2017-04-13'), 21) ,
(DATE('2017-04-14'), 22) ,
(DATE('2017-04-15'), 18) ;
select * from score_streak;
create_date | score
2017-04-01 | 11
2017-04-02 | 8
2017-04-03 | 9
2017-04-06 | 14
2017-04-07 | 15
2017-04-08 | 13
2017-04-12 | 20
2017-04-13 | 21
2017-04-14 | 22
2017-04-15 | 18
I would like to query the table to get all streaks in which the user's score is greater than or equal to 10 and the dates must be consecutive. And each streak has a start date and an end date.
For example, the expected result of the sample data above is provided below (note that there are 3 streaks):
start_date | end_date |streak_count
2017-04-01 | 2017-04-01 | 1
2017-04-06 | 2017-04-08 | 3
2017-04-12 | 2017-04-15 | 4
Thanks.
You can do
SELECT MIN(create_date) start_date,
MAX(create_date) end_date,
COUNT(*) streak_count
FROM (
SELECT q.*,
#g := #g + COALESCE(DATEDIFF(create_date, #p) <> 1, 0) gn,
#p := create_date
FROM (
SELECT *
FROM score_streak
WHERE score > 9
ORDER BY create_date
) q CROSS JOIN (
SELECT #g := 0, #p := NULL
) i
) r
GROUP BY gn
Output:
+------------+------------+--------------+
| start_date | end_date | streak_count |
+------------+------------+--------------+
| 2017-04-01 | 2017-04-01 | 1 |
| 2017-04-06 | 2017-04-08 | 3 |
| 2017-04-12 | 2017-04-15 | 4 |
+------------+------------+--------------+
SQLFiddle

Return unique rows of records for each user

I have a table that tracks different qualifications for students. The qualifications are renewed periodically, so many students have multiple records for the same qualification.
I'm trying to return just the most recent record of each qualification for each student, without the duplicates.
So far I have this, but I'm stuck on what I need to do to remove the duplicate type_scrn and just return the most recent records.
SELECT scrn.*
FROM cert_scrn scrn
WHERE scrn.id_scrn = (SELECT scrn2.id_scrn
FROM cert_scrn scrn2
WHERE scrn.id_scrn = scrn2.id_scrn
AND ( scrn2.type_scrn = 1
OR scrn2.type_scrn = 11
OR scrn2.type_scrn = 12
OR scrn2.type_scrn = 13 )
ORDER BY scrn2.expiredate_scrn DESC LIMIT 1)
ORDER BY scrn.idstu_scrn
This returns:
id_scrn | idstu_scrn | type_scrn | expiredate_scrn
-------------------------------------------------
15 | 58 | 1 | 2010-01-26
1539 | 58 | 1 | 2015-06-21
5790 | 58 | 11 | 2016-02-20
5791 | 58 | 12 | 2016-02-20
5792 | 58 | 13 | 2016-02-20
What I need returned:
id_scrn | idstu_scrn | type_scrn | expiredate_scrn
---------------------------------------------------
1539 | 58 | 1 | 2015-06-21
5790 | 58 | 11 | 2016-02-20
5791 | 58 | 12 | 2016-02-20
5792 | 58 | 13 | 2016-02-20
You need to join to a subquery which finds the max date for each idstu_scn, type_scrn group.
So your query to get the max(date) would be:
select idstu_scrn, type_scrn, max(expiredate_scrn) mdate
from cert_scrn
group by idstu_scrn, type_scrn
Which we then just need to join back to the cert_scrn table again, to find the rest of the details to go along with it.
select scrn.*
from cert_scrn scrn
inner join (
select idstu_scrn, type_scrn, max(expiredate_scrn) mdate
from cert_scrn
group by idstu_scrn, type_scrn ) q
on scrn.idstu_scrn = q.idstu_scrn and scrn.type_scrn = q.type_scrn and scrn.expiredate_scrn = q.mdate
where scrn.type_scrn = 1
or scrn.type_scrn = 11
or scrn.type_scrn = 12
or scrn.type_scrn = 13
demo fiddle here
Please check this one based on Primary Key, appears working fine for this scenario :
SELECT * FROM cert_scrn WHERE id_scrn IN
(SELECT MAX(id_scrn) FROM cert_scrn GROUP BY idstu_scrn, type_scrn);

how to calculate the difference between 2 rows using mysql

I have a table MeterReading in mysql and it has the following columns
meterid bigint(4),
reading bigint(4),
date date,
time time,
consumption,
primary key(meterid,reading)
i am inserting the vlues to this table except consumption and my problem is how to update the table based on these values.
i tried the following query
update MeterReading a
set consumption=(select reading-IFNULL((select MAX(reading) from MeterReading where meterid=a.meterid AND (date < a.date OR date = a.date AND time < a.time )),0));
i want the result like:
meterid | reading | date | time | consumption |
+---------+---------+------------+----------+-------------+
| 1 | 450 | 2012-10-05 | 06:05:05 | 450 |
| 1 | 550 | 2012-10-06 | 08:05:05 | 100 |
| 1 | 600 | 2012-10-07 | 09:05:05 | 50 |
| 1 | 700 | 2012-10-08 | 10:05:05 | 100 |
please help me
Try this instead:
UPDATE MeterReading a
LEFT JOIN
(
SELECT meterid, MAX(reading) MaxReading
FROM MeterReading
GROUP BY meterid
) g ON g.meterid = a.meterid
SET a.consumption = IFNULL(g.MaxReading, 0) - a.reading ;
Update: Use IFNULL(g.MaxReading, 0) - a.reading or you can use the ABS(IFNULL(g.MaxReading, 0) - a.reading).
SQL Fiddle Demo
Edit: So, you need to update each composite key (meterid, reading)'s consumption value with the difference between the current reading value and the next row's reading value. If so you can do this:
UPDATE MeterReading m
INNER JOIN
(
SELECT
m.*,
#rownum := #rownum + 1 AS rank
FROM meterreading m, (SELECT #rownum := 0) r
ORDER BY m.date
) t1 ON m.meterid = t1.meterid
AND m.reading = t1.reading
LEFT JOIN
(
SELECT
m.*,
#rownum2 := #rownum2 + 1 AS rank
FROM meterreading m, (SELECT #rownum2 := 0) r
ORDER BY m.date
) t2 ON t1.rank - t2.rank = 1
SET m.consumption = (t1.reading - IFNULL(t2.reading, 0));
Updated SQL Fiddle Demo
This will make your table meterreading, after update, looks like:
| METERID | READING | DATE | TIME | CONSUMPTION |
-----------------------------------------------------------------------------------------------------
| 1 | 450 | October, 05 2012 02:00:00+0000 | January, 01 1970 06:05:05+0000 | 450 |
| 1 | 550 | October, 06 2012 02:00:00+0000 | January, 01 1970 08:05:05+0000 | 100 |
| 1 | 600 | October, 07 2012 02:00:00+0000 | January, 01 1970 09:05:05+0000 | 50 |
| 1 | 700 | October, 08 2012 02:00:00+0000 | January, 01 1970 10:05:05+0000 | 100 |
Note that: I used the #rownum variable to get each row's rank, and the thing is, this rank is based on the order of the Date column, so it will get the next date's reading value.

mySQL Ranking (and draws)

Next weekend we're having a competition with 3 qualifications a semifinal and a final. Only the best 15 participants could compete in the semifinal. Only the best 6 compete in the Finals.
in the qualifications you get a score from 0 to 100 for each qualification
I'm looking to find a way to select the contesters for the semi-final. This should be based on (rank of qualification1) * (rank of qualification2) * (rank of qualification3)
so i need something like:
select id, name, ((.... as RANK_OF_SCORE_1) * (.. as RANK_OF_SCORE_2) * (... as RANK_OF_SCORE_3)) as qualification_score from participants order by qualification_score desc limit 15
but of course this is not valid mySQL.
Besides this problem if tho contesters have the same score, they should be both included in the semi-finals even if this exceeds the maximum of 15.
For the finals, we would like to select the best 6 of the semi-final scores. If 2 scores are the same we would like to select on the qualifications..
option 1 : use postgres, which support windowing functions (namely RANK() and DENSE_RANK())
SELECT user_id, score, rank() over (order by score desc) from scores;
Time : 0.0014 s
option 2 : use a self- join : the rank of a user with score X is (1 +the count(*) of users with score less than X) ; this is likely to be pretty slow
CREATE TABLE scores( user_id INT PRIMARY KEY, score INT, KEY(score) );
INSERT INTO scores SELECT id, rand()*100 FROM serie LIMIT 1000;
SELECT a.user_id, a.score, 1+count(b.user_id) AS rank
FROM scores a
LEFT JOIN scores b ON (b.score>a.score)
GROUP BY user_id ORDER BY rank;
+---------+-------+------+
| user_id | score | rank |
+---------+-------+------+
| 381 | 100 | 1 |
| 777 | 100 | 1 |
| 586 | 100 | 1 |
| 907 | 100 | 1 |
| 790 | 100 | 1 |
| 253 | 99 | 6 |
| 393 | 99 | 6 |
| 429 | 99 | 6 |
| 376 | 99 | 6 |
| 857 | 99 | 6 |
| 293 | 99 | 6 |
| 156 | 99 | 6 |
| 167 | 98 | 13 |
| 594 | 98 | 13 |
| 690 | 98 | 13 |
| 510 | 98 | 13 |
| 436 | 98 | 13 |
| 671 | 98 | 13 |
time 0.7s
option 3 :
SET #rownum = 0;
SELECT a.user_id, a.score, b.r FROM
scores a
JOIN (
SELECT score, min(r) AS r FROM (
SELECT user_id, score, #rownum:=#rownum+1 AS r
FROM scores ORDER BY score DESC
) foo GROUP BY score
) b USING (score)
ORDER BY r;
time : 0.0014 s
EDIT
SET #rownum1 = 0;
SET #rownum2 = 0;
SET #rownum3 = 0;
SELECT s.*, s1.r, s2.r, s3.r FROM
scores s
JOIN
(
SELECT score_1, min(r) AS r FROM (
SELECT score_1, #rownum1:=#rownum1+1 AS r
FROM scores ORDER BY score_1 DESC
) foo GROUP BY score_1
) s1 USING (score_1) JOIN (
SELECT score_2, min(r) AS r FROM (
SELECT score_2, #rownum2:=#rownum2+1 AS r
FROM scores ORDER BY score_2 DESC
) foo GROUP BY score_2
) s2 USING (score_2) JOIN (
SELECT score_3, min(r) AS r FROM (
SELECT score_3, #rownum3:=#rownum3+1 AS r
FROM scores ORDER BY score_3 DESC
) foo GROUP BY score_3
) s3 USING (score_3)
ORDER BY s1.r * s2.r * s3.r;