MySQL - Get longest chain of rows with a certain value - mysql

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

Related

mysql trying to rank rows by order by

I'd like to calculate a ordered-by rank for my rows. It isn't calculating correctly- I'm only getting a null and 1. I expect to get a rank of 1, 2, 3... based on my joins/order by ladder_name_lh2 = ladder_name_lh1 and date_trigger_event_lh2, and member_id_lh1.
Basically, I am trying to say
If ladder_name_2 matches ladder_name_1 (this is a before/after set up - ladder 2 comes after ladder 1, and there are 6 ladder names (categories), but in order to tell that they're related, I want to make sure that the rank is only being generated if the categories for before and after match) and then generate a rank based on date_2, member_id.
I've written the following:
select
cte_ladder_history_self_join_lh1.member_id_lh1,
cte_ladder_history_self_join_lh1.ladder_config_id_lh1,
cte_ladder_history_self_join_lh1.trigger_name_lh1,
cte_ladder_history_self_join_lh1.trigger_record_id_lh1,
cte_ladder_history_self_join_lh1.date_trigger_event_lh1,
cte_ladder_history_self_join_lh1.ladder_name_lh1,
cte_ladder_history_self_join_lh1.ladder_value_lh1,
cte_ladder_history_self_join_lh1.ladder_change_lh1,
cte_ladder_history_self_join_lh2.member_id_lh2,
cte_ladder_history_self_join_lh2.trigger_name_lh2,
cte_ladder_history_self_join_lh2.trigger_record_id_lh2,
cte_ladder_history_self_join_lh2.date_trigger_event_lh2,
cte_ladder_history_self_join_lh2.ladder_name_lh2,
cte_ladder_history_self_join_lh2.ladder_value_lh2,
cte_ladder_history_self_join_lh2.ladder_change_lh2,
datediff(cte_ladder_history_self_join_lh2.date_trigger_event_lh2,cte_ladder_history_self_join_lh1.date_trigger_event_lh1) as days_spent,
#member_id:=member_id_lh2 AS member_id,
#row_number:=(CASE
WHEN #member_id = member_id_lh1
THEN #row_number + 1
ELSE 1
END) AS rank
from
cte_ladder_history_self_join_lh1 #previous or current ladder history
left join cte_ladder_history_self_join_lh2 on cte_ladder_history_self_join_lh1.member_id_lh1 = cte_ladder_history_self_join_lh2.member_id_lh2
and cte_ladder_history_self_join_lh2.ladder_name_lh2 = cte_ladder_history_self_join_lh1.ladder_name_lh1
and cte_ladder_history_self_join_lh1.ladder_value_lh1 <> cte_ladder_history_self_join_lh2.ladder_value_lh2
and cte_ladder_history_self_join_lh1.date_trigger_event_lh1 < cte_ladder_history_self_join_lh2.date_trigger_event_lh2 # lh2 shows second begin date / end date for lh if exists
order by date_trigger_event_lh2 desc, member_id_lh1 desc;
Which returns the following:
I'm not sure why the ranking isn't working correctly. I'd expect there to be a rank from about 1 - 20 (based on the number of rows that match the ladder_name = advocacy criteria for this person) but I'm getting some wildly different numbers.
Thoughts?
Fixed teh ranking code to read:
CREATE TEMPORARY TABLE cte_leenk_ladder_history_rank
SELECT * ,
#row_number:=(CASE
WHEN #member_id = member_id
THEN #row_number + 1
ELSE 1
END) AS rank,
#member_id := member_id
FROM
cte_leenk_ladder_history_order
where ladder_change = 1
order by salesforce_id desc, date_trigger_event desc;

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 Query to find row duplicates based on condition with limit

I have two tables:
Members:
id username
Trips:
id member_id flag_status created
("YES" or "NO")
I can do a query like this:
SELECT
Trip.id, Trip.member_id, Trip.flag_status
FROM
trips Trip
WHERE
Trip.member_id = 1711
ORDER BY
Trip.created DESC
LIMIT
3
Which CAN give results like this:
id member_id flag_status
8 1711 YES
9 1711 YES
10 1711 YES
My goal is to know if the member's last three trips all had a flag_status = "YES", if any of the three != "YES", then I don't want it to count.
I also want to be able to remove the WHERE Trip.member_id = 1711 clause, and have it run for all my members, and give me the total number of members whose last 3 trips all have flag_status = "YES"
Any ideas?
Thanks!
http://sqlfiddle.com/#!2/28b2d
In that sqlfiddle, when the correct query i'm seeking runs, I should see results such as:
COUNT(Member.id)
2
The two members that should qualify are members 1 and 3. Member 5 fails because one of his trips has flag_status = "NO"
You could use GROUP_CONCAT function, to obtain a list of all of the status ordered by id in ascending order:
SELECT
member_id,
GROUP_CONCAT(flag_status ORDER BY id DESC) as status
FROM
trips
GROUP BY
member_id
HAVING
SUBSTRING_INDEX(status, ',', 3) NOT LIKE '%NO%'
and then using SUBSTRING_INDEX you can extract only the last three status flags, and exclude those that contains a NO. Please see fiddle here. I'm assuming that all of your rows are ordered by ID, but if you have a created date you should better use:
GROUP_CONCAT(flag_status ORDER BY created DESC) as status
as Raymond suggested. Then, you could also return just the count of the rows returned using something like:
SELECT COUNT(*)
FROM (
...the query above...
) as q
Although I like the simplicity of fthiella's solution, I just can't think of a solution that depends so much on data representation. In order not to depend on it you can do something like this:
SELECT COUNT(*) FROM (
SELECT member_id FROM (
SELECT
flag_status,
#flag_index := IF(member_id = #member, #flag_index + 1, 1) flag_index,
#member := member_id member_id
FROM trips, (SELECT #member := 0, #flag_index := 1) init
ORDER BY member_id, id DESC
) x
WHERE flag_index <= 3
GROUP BY member_id
HAVING SUM(flag_status = 'NO') = 0
) x
Fiddle here. Note I've slightly modified the fiddle to remove one of the users.
The process basically ranks the trips for each of the members based on their id desc and then only keeps the last 3 of them. Then it makes sure that none of the fetched trips has a NO in the flag_status. FInally all the matching meembers are counted.

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.

MySQL: Limiting number of results received based on a column value | Combining queries

I've done research on this problem, but am having trouble finding a solution.
I have the following query that gives me a list of "some_id"s:
SELECT some_id FROM example GROUP BY some_id
And I have the following query that will get a list of the 5 most recent entries for a row that has "some_id" equal to a number.
SELECT * FROM example
WHERE some_id = 1
ORDER BY last_modified DESC
LIMIT 5
How can I get the the top 5 most recent entries from the table "example" for each "some_id", using only one query? If there are less than 5 entries for a "some_id", it is okay to include them, if that makes things less complex.
Many thanks!
Found the answer when looking at the first answer in the following post:
How do I limit the number of rows per field value in SQL?
I've modified it to fit my specific needs:
SELECT * FROM
(
SELECT *, #num := if(#some_id = some_id, #num := #num + 1, 1) as row_num,
#some_id := some_id as some_id
FROM example
ORDER BY last_modified DESC
) as e
WHERE row_num <= 5
Hi Please use Group by and having clause for check limit for every id..
SELECT * FROM `example`
GROUP BY some_id
HAVING count( * ) <= 5
ORDER BY last_modified DESC
It will check for every some_id and check if number of rows for some_id is <=5 then it will be display result.