Cumulative sum query on foreign key - mysql

I want to write a query for cumulative sum in MYSQL. I have a foreign key in my table and I want to add their hours as a cumulative sum.
Table 1
id(not primary key) Hours
1 4
2 4
1 5
I have tried this query
select spent_hours := spent_hours + hours as spent
from time
join (select spent_hours := 0) s
I am getting this
id(not primary key) hours spent
1 4 4
2 4 8
1 5 13
But I want this result:
id(not primary key) Hours spent
1 4 4
2 4 4
1 5 9

Since you have an autoincrement field (let's assume for this case its called record_id) you can use this little trick to achieve what you want:
SELECT Main.id, Main.spentHours,
(
SELECT SUM(spentHours)
FROM Table1 WHERE Table1.id = Main.id
AND Table1.record_id >= Main.record_id
) as totalSpentHours
FROM Table1 Main
ORDER BY Main.record_id ASC
This will fetch the id, current spent hours, along using a subselect, all hours from the current ID and above for that user.

You need additional an variable to keep track of the cumulative sum within each id:
select t.id, t.hours,
(#h := if(#i = id, #h + spent_hours,
if(#i := id, spent_hours, spent_hours)
)
) as spent
from time cross join
(select #h := 0, #i := 0) params
order by id, ??;
Note: you need an additional column to specify the order for the cumulative sum (indicated by ?? in the order by clause. Remember that SQL tables represent unordered sets, so you need a column to explicitly represent ordering.

Related

Student absent for 5 consecutive days excluding Holidays

I am using code igniter and attendance table as below:
attendance ID timestamp student_id status
1 01-01-20 1 P
2 01-01-20 2 P
3 02-01-20 1 P
4 02-01-20 2 A
5 03-01-20 1 P
6 03-01-20 2 A
7 04-01-20 1 H
8 04-01-20 2 H
9 05-01-20 1 P
10 05-01-20 2 A
My target is to get the student id who is absent for 3 consecutive days for the period of the last 1 month from today excluding the holidays in the middle like in the above table the student id 2 should be the one with 3 consecutive absence excluding the holiday on the 4th Jan.
I am using Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.28 with mysql. I have been able to get the consecutive 3 absent but when there is a holiday in the middle, I fail to find a work around there. here is my existing code:
SELECT *,
CASE
WHEN (#studentID = student_id) THEN #absentRun := IF(status = A, 0, #absentRun + 1)
WHEN (#studentID := student_id) THEN #absentRun := IF(status = A, #absentRun + 1, 0)
END AS absentRun
FROM attendance
Where timestamp BETWEEN (CURRENT_DATE() - INTERVAL 1 MONTH) AND CURRENT_DATE() AND year='2019-2020'
ORDER BY student_id, timestamp;
I would really really appreciate a quick answer from someone to help me with this. I am really hope to have a solution since I have posted here for help for the first time. Thanks in advance.
I understand this as a variant of the gaps and island problem. Here is one way to solve it using row_number() (available in MySQL 8.0) - the difference between the row numbers gives you the group each record belongs to.
select
student_id,
min(timestamp) timestamp_start,
max(timestramp) timestamp_end
from (
select
t.*,
row_number() over(partition by student_id order by timestamp) rn1,
row_number() over(partition by student_id, status order by timestamp) rn2
from mytable t
) t
where status = 2
group by student_id, rn1 - rn2
having count(*) >= 5
This will give you one record for each streak of at least 5 consecutive days of absence for each student. As a bonus, the query also displays the starting and ending date of each streak.

Sum Top 10 Values

I’ve searched and I know this has been asked before but I am struggling to get my head around what I can / can’t do.
My cycling club records race results each time a rider has entered a race. Each result is awarded points - 50 for 1st, 49 for 2nd etc.
So the table looks like
resultid(pk) | riderid(fk) | leaguepts
1 1 50
2 2 49
3 3 48
4 1 50
5 2 42
6 3 50
7 4 30
...etc
I am trying to extract the sum of top 10 points awarded for each riderid from the results table.
(the actual database is a bit more complicated with a table for rider name / rider id and also a race table so we can display the results of each race etc but I just want to get the basic league table query working first of all)
So I want to extract the sum of the top 10 best scores for each rider. Then display each riders score, in a descending league table.
So far I’ve only had success using UNION ALL e.g.
SELECT sum(points) AS pts from
(
SELECT points from `results`
WHERE riderid = 1
ORDER BY points DESC
LIMIT 10
) as riderpts
UNION ALL
SELECT sum(points) AS pts from
(
SELECT points from `results`
WHERE riderid = 2
ORDER BY points DESC
LIMIT 10
) as riderpts
ORDER BY pts DESC
But there could be up to 90-odd riders who have registered at least one score so this query could get very big.
I found this which looks like it should work for me but doesn't. Sum top 5 values in MySQL I changed the column names for my table but it seems to sum all results, not the top 10 for each rider.
Alternatively I could just issue a query for each rider id. Not good I guess?
Subquerying is a problem because I can't limit on the inner query?
Run a job (manual or cron) to update the league table periodically and just display the table results?
Edit (not sure if this is the correct etiquette or I should start a new thread?). Gordon answered the question below but in the meantime I tried to work this out for myself using one of the links below. I could get results that returned the top 10 scores for each rider with the query below
set #riderid = '';
set #riderrow = 1;
select riderid, leaguepts, row_number
from
(
select
riderid,
leaguepts,
#riderrow := if(#riderid = riderid, #riderrow + 1, 1) as row_number,
#riderid := riderid as dummy
from wp_tt_results order by riderid, leaguepts desc
) as x where x.row_number <= 10;
BUT I can't see what I would need to do next to get the sum of top 10 results per riderid?
In MySQL, the easiest way to do this is probably to use variables:
SELECT riderid, sum(points)
FROM (SELECT r.*,
(#rn := if(#r = riderid, #rn + 1,
if(#r := riderid, 1, 1)
)
) as seqnum
FROM results r CROSS JOIN
(SELECT #r := 0, #rn := 0) as wnw
ORDER BY riderid, points DESC
) r
WHERE seqnum <= 10
GROUP BY riderid;

MySQL - Get longest chain of rows with a certain value

I can simplify my table structure for the purposes of this question to the following:
http://sqlfiddle.com/#!2/dcdd3/1
I want to get the longest win streak for each user (i.e. the largest number of contiguous win=1 rows without a win=0 row in between, returned in the following format:
user_id | win_streak
--------------------
1 | 5
2 | 3
3 | 3
The current solution I have is to get all of the rows and build the results in a PHP foreach loop, but I can't help thinking that there is a way to do this in MySQL.
You need a column that defines the order of the wins. I was assuming this to be the auto_increment column id:
select
user_id, max(wins) as longest_winning_streak
from (
SELECT
ugr.*
, #winstreak := if(#prev_user = user_id, if(won = 1, #winstreak + 1, 0), 1) as wins
, #prev_user := user_id
FROM `user_game_results` ugr
, (SELECT #winstreak := 0, #prev_user := null) var_init
ORDER BY user_id, id
) sq
group by user_id
see it working live in an sqlfiddle
Your desired result is not quite correct, user_id has 3 wins in a row.
Take a look at this previous answer:
http://stackoverflow.com/questions/15484908/mysql-count-the-number-of-consecutive-times-a-value-appears

Sorting a MySQL table with a given ordering and empty random values

I have a MySQL table Section which has a column ordering. Some of the values of ordering are numbers and they are mutually exclusive (no two numbers are the same) and others are simple NULL.
Example:
ID Ordering
-- --------
1 NULL
2 2
3 5
4 NULL
5 NULL
6 3
7 NULL
Now I want to sort this table and get the following result:
(ROW NUMBER) ID Ordering
------------ -- -------- ------------
1 4 NULL
2 2 2
3 6 3
4 5 NULL
5 3 5
6 7 NULL
7 1 NULL
I.e. I want the rows that have a non-NULL value to appear in the given ordering, but the ones that have a NULL value to appear in the remaining ones, in random order (not predictable).
I am sure there are always enough NULL's to fill up the empty spaces.
I was thinking about first getting the ones with an order (WHERE ordering IS NOT NULL) and then the ones without an order (WHERE ordering IS NULL) and then somehow create an extra column that transforms the ones with NULL into a number that doesn't appear in the former.
Any help would be very much appreciated. I am using this in a project where there are sections that have a fixed position, but others can set to have a random order. Every time the page loads the random sections should be displayed, well, randomly.
You can use the nvl() function to change the NULL result for other value than 0
I have found a solution, but it is rather clumsy and big. I'll explain the reasoning:
Get a list of numbers from 1 to the number of rows (and call it all_rows).
Get the Sections whose orderings are NOT NULL.
Substract those two, i.e. get from all_rows the ones that are not in the list of already existing orderings and give them a row number. (Call this unused_row_numbers)
Get the Sections whose orderings are NULL, randomize them and give them a row number. (Call this nulls_with_row_numbers)
Join unused_row_numbers with nulls_with_row_numbers to get all the Sections whose ordering is NULL and now have one of the missing/unused row numbers.
Lastly, make the union of this last one with the ones that already have an ordering and tataaaa...
Now here's the SQL:
SELECT row_number, id
FROM (
SELECT #curRow2 := #curRow2 + 1 AS row_number2, row_number
FROM (
SELECT #curRow := #curRow + 1 AS row_number
FROM Sections
JOIN (SELECT #curRow := 0) r
) all_rows
JOIN (SELECT #curRow2 := 0) r
WHERE all_rows.row_number NOT IN (
SELECT ordering
FROM Sections
WHERE ordering IS NOT NULL
)) unused_row_numbers,
(
SELECT #curRow3 := #curRow3 + 1 AS row_number3, id
FROM (
SELECT ordering, id
FROM Sections
WHERE ordering IS NULL
ORDER BY RAND()
) randomized_nulls
JOIN (SELECT #curRow3 := 0) r
) nulls_with_row_numbers
WHERE unused_row_numbers.row_number2 = nulls_with_row_numbers.row_number3
UNION
SELECT ordering AS row_number, id
FROM Sections
WHERE ordering IS NOT NULL
ORDER BY row_number
It gives different results each time. Example result:
ROW_NUMBER ID
---------- --
1 1 (random)
2 2 (always second position)
3 6 (always third position)
4 4 (random)
5 3 (random)
6 5 (always sixth position)
7 7 (random)

mysql rank from results

Sorry for posting another question about mysql ranking but all questions and answers which I already looked didn't help me....
I have mysql table of user points. User can have more results. My goal is to get max result from user and its rank.
CREATE TABLE results
(`user_id` int, `points` int);
INSERT INTO results VALUES
(1,10),
(2,20),
(3,20),
(4,30),
(4,60),
(5,5),
(1,80);
So upper solution would be:
rank | user_id | points
1 1 80
2 4 60
3 3 20
3 2 20
4 5 5
The following query does the trick:
SET #rank=0;
SET #points=0;
SELECT #rank := IF(#points = a.points, #rank, #rank + 1) AS rank, a.user_id, #points := a.points AS points
FROM (
SELECT user_id, MAX(points) as points
FROM results
GROUP BY user_id
) a
ORDER BY a.points DESC;
I have also created an SQLFiddle of it so you can see that it works: http://sqlfiddle.com/#!2/7ba2f/12
Use a user defined variable to produce the rank when selecting from an aggregated aliased query that calculates the maximum for each user:
select
(#rank := ifnull(#rank, 0) + 1) as rank,
user_id,
points
from (select
user_id,
max(points) as points
from results
group by 1
order by 2 desc) x
FYI, a UDV starts out life as null, hence the ifnull() call.