I'm stuck and this is driving me crazy... I'd like to use the code below to get x number of wins in the row. The problem is, mysql only allows 1 correlation level (not sure if im explaining this right) but #id is supposed to return the players id from the first select query. Any idea on how I can make this work?
select (#rank := if(#points = points, #rank +1, if(#points := points, #rank + 1, #rank + 1 ))) as rank, er.* from (select
#id := cc6_MensLeague_players.id as `id`,
#rounds := (
ifnull((select sum(p1.w)
from (
select
#r1 := (case when p1_r1 like '%' then 1 else 0 end) as r1,
#r2 := (case when p1_r2 like '%' then 1 else 0 end) as r2,
#r3 := (case when p1_r3 like '%' then 1 else 0 end) as r3,
#r4 := (case when p1_r4 like '%' then 1 else 0 end) as r4,
(#r1+#r2+#r3+#r4) as w
from cc6_MensLeague_scoresheets where p1 = #id
)p1),0)
) as `rounds`,
sum((#rounds*2)+(#rounds*1)) as `points`
from cc6_MensLeague_players group by `id`) er cross join (select #rank := 0, #points := -1) params order by id desc limit 9;
Here is some sample data to play with:
(Using dynamic #id alias will not work)
https://www.db-fiddle.com/f/ao2zgyiy8U5doGER6XZT23/3
(Using static id 8 works perfectly)
https://www.db-fiddle.com/f/2f2KvZt5MHVUuaP3WLPmAi/2
Here is what the resulting output should be:
rank id rounds points
1 10 4 12
2 9 4 12
3 8 8 24
4 7 8 24
5 6 6 18
6 5 4 12
7 4 0 0
8 3 0 0
9 2 0 0
Thanks for the help
Related
I'm trying to produce an SQL statement to SUM top 5 values within categories per userID to create an overall total. Is this possible and how do I achieve it? I can sum the top 5 per single category or ALL but struggling to see how I can SUM each separate category total together.
For example,
ID Cater Weight
--------------------------------
1 Cheese 10
2 Bacon 15
1 Cheese 5
2 Bacon 10
1 Cheese 22
2 Cheese 5
1 Bacon 10
1 Cheese 10
2 Cheese 5
1 Cheese 20
2 Bacon 10
1 Cheese 30
The results i'm looking for is,
ID Total_Weight
-------------------
1 102 Top 5 Cheese (10+22+10+20+30) + Top 5 Bacon (10)
2 45 Top 5 Cheese (5+5) + Top 5 Bacon (15+10+10)
Any values outside of the Top 5 are ignored.
The code below displays the SUM of the top 5 weights from ALL categories AS total weight. Can I achieve what I want from a single statement?
$log = "SELECT id, cater,
SUM(weight) AS total_weight
FROM ( SELECT id,
CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1
ELSE #ROW_NUMBER := 1
END AS rn,
cater,
weight,
#id := id
FROM individual,
(SELECT #ROW_NUMBER := 1, #ID := '') r
ORDER
BY
id, weight DESC
) TMP
WHERE rn <= 5
AND cater <> ''
GROUP
BY id
ORDER
BY total_weight DESC";
There might have some other better solution. But this will provide your expected result-
SELECT B.id,
SUM(T_weight) Total_Weight,
group_concat(concat('TOP 5 ',B.cater,' (',B.T,')') SEPARATOR ' ') Details
FROM
(
SELECT ID,cater,SUM(Weight) T_weight,group_concat(weight SEPARATOR '+') T
FROM
(
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cheese' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Bacon' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat3' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat4' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat5' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat6' ORDER BY id, weight DESC
)A WHERE rn < 6
)A
GROUP BY ID,Cater
)B
group by id
The output is-
1 191 TOP 5 Cheese (10+22+20+10+30) TOP 5 Cat3 (25+9+20+16+13) TOP 5 Bacon (10)
2 45 TOP 5 Cheese (5+5) TOP 5 Bacon (15+10+10)
If you have MySql version 8 or higher, you can use this code:
SELECT id, SUM (weight)
FROM (SELECT test.*,
ROW_NUMBER ()
OVER (PARTITION BY id, categ ORDER BY weight DESC)
rn
FROM test) sub
WHERE sub.rn < 6
GROUP BY id;
You need MySql 8+ because I used the function row_number and only from version 8 it exists.
In the below fiddle example, I used Microsoft Sql Server 2017 because they didn't had MySql 8 or higher.
You can see your example here.
I have table like below, start_t is my timestamp in unixtime for example (1438326239412) but for simplicity I wrote small numbers here:
user_id | start_t | duration
1 12 1
1 15 2
1 4 5
2 9 6
2 10 5
3 9 6
I want to get first N rows for each user_id between two time stamps. This is my code but it returns more than the limit I want:
SELECT us.* FROM (SELECT us.*, (#rn := if(#i = us.user_id, #rn + 1, if(#i := us.user_id, 1, 1) ) ) AS seqnum FROM user_stats us,tourn_user tu CROSS JOIN (SELECT #rn := 0, #i := -1) params WHERE (us.start_t+us.duration)<= 20 AND us.start_t >= 4 ORDER BY us.user_id, start_t ASC ) us WHERE seqnum <= 1
The result should look like this for that specific example:
user_id | start_t | duration
1 4 5
2 9 6
3 9 6
Here is the solution in case someone wants:
select us.* from (select us.*, (#rn := if(#i = us.user_id, #rn + 1, if(#i := us.user_id, 1, 1) ) ) as seqnum from user_stats us cross join (select #rn := 0, #i := -1) params where (us.start_t+us.duration)<= 15 AND us.start_t >= 0 order by us.user_id, start_t ASC ) us where seqnum <= 1
I've got a table like this:
id name incidence placeRef
1 John 10 1
2 Ann 9 1
3 Paul 9 1
4 Carl 8 1
5 John 4 1
6 Ann 4 1
7 Paul 7 1
8 Carl 1 1
I want to rank these using the ordinal ranking method. Which would add ranks to my table as such:
id name incidence placeRef rank
1 John 10 1 1
2 Ann 9 1 2
3 Paul 9 1 2
4 Carl 8 1 4
5 John 4 1 2
6 Ann 4 1 2
7 Paul 7 1 1
8 Carl 1 1 4
How can this be achieved?
N.B. I am going to answer my own question, but would like to know if anyone has any better solutions as it is a bit hacky; though I found numerous posts recommending hacks for this situation.
You can do this with variables or a correlated subquery. The subquery method looks like this:
select t.*,
(select 1 + count(t2.incidence)
from table t2
where t2.incidence > t.incidence
) as rank
from table t;
The variables method is a bit trickier because you have to remember the number of matching rows with a given value:
select t.*,
(#rn := if(#i = t.incidence, if(#cnt := #cnt + 1, #rn, #rn),
#cnt + if(#i := t.incidence, if(#cnt := 1, #rn, #rn), #rn)
)
) as rank
from table t cross join
(select #i := 0, #rn := 0, #cnt := 1) vars
order by incidence desc;
EDIT:
If you want to update the table, just use update with a join:
update table t join
(<either subquery above>) s
on t.id = s.id
set t.rank = s.rank;
The following works:
UPDATE names
JOIN ( SELECT * FROM names ORDER BY placeRef, incidence DESC ) AS p ON p.id = names.id,
( SELECT #curRank := 0, #nextRank := 0, #prevInc := 9999999999, #prevPlace := 0 ) AS v
SET
names.rank = IF( #prevPlace != p.placeRef, #curRank := 0, 0 ),
names.rank = IF( #prevPlace != p.placeRef, #nextRank := 0, 0 ),
names.rank = IF( #prevInc = p.incidence, #nextRank := #nextRank + 1, #curRank := #nextRank := #nextRank + 1 ),
names.rank = IF( #prevInc = p.incidence, #curRank := #curRank, #curRank := #nextRank ),
names.incidence = #prevInc := names.incidence,
names.placeRef = #prevPlace := names.placeRef;
Explanation:
UPDATE names
1 - Sets the table to be updated
JOIN ( SELECT * FROM names ORDER BY placeRef, incidence DESC ) AS p ON p.id = names.id,
2 - This makes a virtual table with the results ordered, so that rankings can be applied
( SELECT #curRank := 0, #nextRank := 0, #prevInc := 9999999999, #prevPlace := 0 ) AS v
3 - This set some variables that will be used to tell when to incrament and reset the rank
names.rank = IF( #prevPlace != p.placeRef, #curRank := 0, 0 ),
4 - This is a hack that resets the current rank to 0 when MySQL iterates into a new place
names.rank = IF( #prevPlace != p.placeRef, #nextRank := 0, 0 ),
5 - This is a hack that resets the next rank to 0 when MySQL iterates into a new place
names.rank = IF( #prevInc = p.incidence, #nextRank := #nextRank + 1, #curRank := #nextRank := #nextRank + 1 ),
6 - This is a hack that updates the next and current ranks when the current incidence is the same as the previous incidence
names.rank = IF( #prevInc = p.incidence, #curRank := #curRank, #curRank := #nextRank ),
7 - This sets the rank to the same as the last rank when its incidence is the same as the previous or increments the rank if it isn't
names.incidence = #prevInc := names.incidence,
8 - This is a hack that sets a variable to contain the previous incidence, so we can tell what to do in the next itteration
names.placeRef = #prevPlace := names.placeRef;
9 - This is a hack that sets a variable to contain the previous place, so we can tell what to do in the next itteration
This is my code and works for ties but it does not skip position on ties
SELECT `item`, (`totalrate` / `nrrates`),
#rank_count := #rank_count + (totalrate/nrrates < #prev_value) rank,
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
ORDER BY avg DESC
Here is the out I get
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 5 9.5
I want
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 6 9.5
To skip the 5 position
I would like to merge with this that does skip position on ties
(I took the below code from this post
MySQL Rank in the Case of Ties)
SELECT t1.name, (SELECT COUNT(*) FROM table_1 t2 WHERE t2.score > t1.score) +1
AS rnk
FROM table_1 t1
how would I modify my code to get it to skip position with the above code it looks simple but i haven't figured it out.
Thanks
On ties, you may want to skip and use current row num to next unmatched avg value row as next rank.
Following should help you
SELECT `item`, #curr_avg := ( `totalrate` / `nrrates` )
, case when #prev_avg = #curr_avg then #rank := #rank
else #rank := ( #cur_row + 1 )
end as rank
, #cur_row := ( #cur_row + 1 ) as cur_row
, #prev_value := #curr_avg avg
FROM table
, ( SELECT #prev_avg := 0, #curr_avg := 0
, #rank := 0, #cur_row := 0 ) init
ORDER BY avg DESC
Similar examples:
To display top 4 rows using rank
Mysql Query for Rank (RowNumber) and Groupings
Update a field with an incrementing value that resets based on
field
Here's another alternative. First, the averages are calculated. If they are already available in a table, it would be even easier (as can be seen in the fiddle demo). Anyways, the rank is based on the logic of counting how many items have a lesser average than the current item.
SELECT
A1.`item`,
A1.avg,
COUNT(A2.`item`) avg_rank
FROM
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A1 --alias for the inline view
INNER JOIN
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A2 --alias for the inline view
ON A2.avg < A1.avg
GROUP BY A1.id, A1.avg
ORDER BY A1.avg;
SQL Fiddle demo
I have this Table , (sequence_No.) Field is null :
ID Name age sequence_No.
-- ----- --- ------------
1 sara 20
2 sara 20
3 sara 20
4 john 24
5 john 24
6 Hama 23
I want to Update it to this:
ID Name age sequence_No.
-- ----- --- ------------
1 sara 20 1
2 sara 20 2
3 sara 20 3
4 john 24 1
5 john 24 2
6 Hama 23 1
Which query can do that in mysql?
thank you
You can emulate ROW_NUMBER() using correlated subquery in mysql. The resulting table with sequential number will be join with the table itself and update the value of sequence_No using the generated numbers.
UPDATE tableName a
INNER JOIN
(
SELECT A.ID,
(
SELECT COUNT(*)
FROM tableName c
WHERE c.Name = a.Name AND
c.ID <= a.ID) AS sequence_No
FROM TableName a
) b ON a.ID = b.ID
SET a.sequence_No = b.sequence_No
SQLFiddle Demo
SELECT ID, Name, age, sequence_No
FROM
(
select ID,
Name,
age,
#sum := if(#nme = Name AND #acct = age, #sum ,0) + 1 sequence_No,
#nme := Name,
#acct := age
from TableName,
(select #nme := '', #sum := 0, #acct := '') vars
order by Name, age
) s
ORDER BY ID
Or you may use
SELECT
ID,
Name,
age,
(
CASE Name
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := Name END
) + 1 AS sequence_No
FROM student, (SELECT #curRow := 0, #curType := '') r
ORDER BY ID,NAME;
this would work though i have not tested yet you can use the same to update your table