I currently have two really complex queries that I use to bring in scores with ranks. The first is all the participants and their scores with stage ranks, the second is Grouped by each participant and calculates their overall rank (sum of stage ranks).
First query to get all participants for each stage with scores and ranks
SELECT * FROM (
SELECT alias, catabbr, sh_dq, raw, sdq, dnf,
#rownum := IF(#stage != stage_name, 1, #rownum + 1) AS rowNum,
#rank := IF(#prevVal != cTime, #rownum, #rank) AS rank,
#stage := stage_name as stage_name,
#prevVal := cTime AS cTime,
id, zeroTime
FROM
(SELECT #rownum:= 0) rn,
(SELECT #rank:= 1) av,
(SELECT #stage:= '') sv,
(SELECT #prevVal:= 0) pv,
(SELECT s.stage_name stage_name, sc.alias alias, sc.catAbbr catabbr, sc.sh_dq sh_dq, sc.time raw, sc.id id, sc.sdq sdq, sc.dnf dnf,
IF(sc.time = "0", 1, 0) AS zeroTime,
((ROUND(sc.time, 2) + (sc.miss * 5)) + ((sc.proc * 10) + (sc.saf * 10) + (sc.sog * 30)) - (sc.bthPoints)) cTime
FROM matches m
LEFT JOIN stages s ON s.match_id = m.id
LEFT JOIN scores sc ON sc.stage_id = s.id
WHERE m.matchuuid = 'E13A4C61-A2B8-48E2-BE1B-1FFB77CC5849'
GROUP BY sc.alias, s.stage_name
ORDER BY s.stage_name, zeroTime, cTime
) tv
) t
ORDER BY zeroTime, alias, stage_name;
Outputs:
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
| alias | catabbr | sh_dq | raw | sdq | dnf | rowNum | rank | stage_name | cTime | zeroTime |
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
| Back Bob | S | 0 | 52.9799995422 | 0 | 1 | 54 | 54 | Stage 1 | 92.98 | 0 |
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
| Back Bob | S | 0 | 43.9099998474 | 0 | 0 | 46 | 46 | Stage 2 | 48.91 | 0 |
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
| Ben Scal | ES | 0 | 26.9699993134 | 0 | 0 | 27 | 27 | Stage 1 | 31.97 | 0 |
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
| Ben Scal | ES | 0 | 32.8800010681 | 0 | 0 | 38 | 38 | Stage 2 | 42.88 | 0 |
+-----------+---------+-------+-----------------+-----+-----+--------+------+-----------------------+-------+----------+
Second query groups participants by name and sums their ranks for a final rank
SELECT alias, catabbr, SUM(cTime) fTime, SUM(rank) fRank, zeroTime, rank
FROM (
SELECT alias, catabbr, sh_dq, raw, sdq, dnf,
#rownum := IF(#stage != stage_name, 1, #rownum + 1) AS rowNum,
#rank := IF(#prevVal != cTime, #rownum, #rank) AS rank,
#stage := stage_name as stage_name,
#prevVal := cTime AS cTime,
id, zeroTime
FROM
(SELECT #rownum:= 0) rn,
(SELECT #rank:= 1) av,
(SELECT #stage:= '') sv,
(SELECT #prevVal:= 0) pv,
(SELECT s.stage_name stage_name, sc.alias alias, sc.catAbbr catabbr, sc.sh_dq sh_dq, sc.time raw, sc.id id, sc.sdq sdq, sc.dnf dnf,
IF(sc.time = "0", 1, 0) AS zeroTime,
((ROUND(sc.time, 2) + (sc.miss * 5)) + ((sc.proc * 10) + (sc.saf * 10) + (sc.sog * 30)) - (sc.bthPoints)) cTime
FROM matches m
LEFT JOIN stages s ON s.match_id = m.id
LEFT JOIN scores sc ON sc.stage_id = s.id
WHERE m.matchuuid = 'E13A4C61-A2B8-48E2-BE1B-1FFB77CC5849'
GROUP BY sc.alias, s.stage_name
ORDER BY s.stage_name, zeroTime, cTime
) tv
) t
GROUP BY alias
ORDER BY zeroTime, fRank, fTime;
Outputs:
+-----------+-----------+-----------+-------+-----------+
| alias | catabbr | fTime | fRank | zeroTime |
+-----------+-----------+-----------+-------+-----------+
| Back Bob | S | 141.89 | 100 | 0 |
+-----------+-----------+-----------+-------+-----------+
| Ben Scal | ES | 74.85 | 68 | 0 |
+-----------+-----------+-----------+-------+-----------+
The zeroTime column makes it so I can sort by times that are greater than 0, and correctly calculate the rank.
When I try to join them I get no results or anything. Is there a way to combine/join these two queries into one query so that they will output the following?
+-----------+-----------+-----------+-------+-----------+---------------+---------------+---------------+---------------+
| alias | catabbr | fTime | fRank | zeroTime | Stage 1 Time | Stage 1 Rank | Stage 2 Time | Stage 2 Rank |
+-----------+-----------+-----------+-------+-----------+---------------+---------------+---------------+---------------+
| Ben Scal | ES | 74.85 | 68 | 0 | 31.97 | 27 | 42.88 | 38 |
+-----------+-----------+-----------+-------+-----------+---------------+---------------+---------------+---------------+
| Back Bob | S | 141.89 | 100 | 0 | 92.98 | 54 | 48.91 | 46 |
+-----------+-----------+-----------+-------+-----------+---------------+---------------+---------------+---------------+
This will be going into a php page, so I can loop through and pull in what I need, the main thing I'm trying to do is join these two queries.
My structures and data are too big for a sqlfiddle.
Here is the structure and inserts for matches and stages: http://pastebin.com/YvZevd5j
Here is the structure and inserts for scores: http://pastebin.com/jLBYxMvc
One thing to note too, is that a game could have 1 or more stages and 1 or more particpants.
This Answer may not scale well as you would need to know the number of stages, but perhaps you can handle building it with a loop or something in you application. Here's a simplified version, you can modify it to work with your data.
SELECT
alias,
catAbbr,
sum(fTime) as fTime,
sum(fRank) as fRank,
sum(zeroTime) as zeroTime,
sum(Stage_1_Time) as stage_1_Time,
sum(Stage_1_Rank) as Stage_1_Rank,
sum(Stage_2_Time) as stage_2_Time,
sum(Stage_2_Rank) as Stage_2_Rank
FROM (
SELECT
alias,
catAbbr,
0 as fTime,
0 as fRank,
0 as zeroTime,
Stage_1_Time as stage_1_Time,
Stage_1_Rank as Stage_1_Rank,
0 as stage_2_Time,
0 as Stage_2_Rank
FROM (query that gets stage 1 info)
UNION
SELECT
alias,
catAbbr,
0 as fTime,
0 as fRank,
0 as zeroTime,
0 as stage_1_Time,
0 as Stage_1_Rank,
stage_2_Time as stage_2_Time,
Stage_2_Rank as Stage_2_Rank
FROM (query that gets stage 2 info)
UNION
SELECT
alias,
catAbbr,
fTime as fTime,
fRank as fRank,
zeroTime as zeroTime,
0 as stage_1_Time,
0 as Stage_1_Rank,
0 as stage_2_Time,
0 as Stage_2_Rank
FROM (query that gets averaged info)
) temp
GROUP BY alias, catAbbt
Related
I have a table orders like:
order_id int ,type string ,weight double.
Example:
| 4 | type1 | 9.729999542236328 |
| 5 | type2 | 13.930000305175781 |
| 14 | type4 | 9.399999618530273 |
| 17 | type1 | 3.490000009536743 |
| 20 | type3 | 6.349999904632568 |
| 25 | type3 | 12.869999885559082 |
| 31 | type4 | 1.3700000047683716 |
| 40 | type5 | 20.079999923706055 |
| 42 | type2 | 9.0600004196167 |
| 45 | type2 | 15.390000343322754 |
I want to get rows grouped by id where the total weight is less than 500.
Example:
{order_ids: [1, 2, 3], total_weight: 450 }
{order_ids: [4, 5, 6], total_weight: 470 }
{order_ids: [7, 8, 9], total_weight: 400 }
I want to get the ids' of the orders and the total weight of them. I have 200k+ lines on the table so performance is a big focus for me. I haven't shared any query because I don't know where to start.
I am using golang with gorm and mysql 8.0.21.
I don't need to find the optimal solution it can be FIFO.
SELECT GROUP_CONCAT(order_id ORDER BY order_id) order_ids,
SUM(weight) total_weight
FROM (SELECT test.*,
#current_group := #current_group + (#current_weight + weight > #max_weight) group_number,
#current_weight := weight + #current_weight * (#current_weight + weight <= #max_weight) cumulative_weight
FROM test, (SELECT #current_weight := 0, #current_group := 0) variables
ORDER BY order_id) subquery
GROUP BY group_number;
fiddle
PS. Of course this query cannot find optimal "cutting stock problem" solution.
You can use a recursive CTE:
with tt as (
select tt.*,
row_number() over (order by rand()) as seqnum
from t
),
recursive cte (
select order_id, weight, weight as running_weight, 1 as grp
from tt
where seqnum = 1
union all
select tt.order_id, tt.weight,
(case when tt.weight + cte.running_weight >= 500
then tt.weight else tt.weight + cte.running_weight
end),
(case when tt.weight + cte.running_weight >= 500
then grp + 1 else grp
end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte;
I have the following ranking system.
SET #1=0;
SELECT id, username, magic_xp, #i:=#i+1 AS rank
FROM hs_users
ORDER
BY magic_xp DESC;
hs_users
id username magic_xp rank
988 5hapescape 14926854 1
737 Ozan 13034431 2
989 Kurt 13034431 3
6 LEGACY 0 4
11 Bobby 0 5
276 Bobby123 0 6
345 Mynamesjason 0 7
450 Demon Spawn 0 8
987 Satan 0 9
As you see I have 2 users have the same xp.
I want to make them both have rank = 2 and the rest should follow from 3.
How can I group them like this?
| username | magic_xp | rank |
| ---------- + -------- + ---- |
| ShapeScape | 1000 | 1 |
| Kurt | 100 | 2 |
| Ozan | 100 | 2 |
| Legacy | 10 | 3 |
In MySQL, the most efficient way is to use variables:
select t.*,
(#rank := if(#magic_xp = magic_xp, #rank,
if(#magic_xp := magic_xp, #rank + 1, #rank + 1)
)
) as rank
from table t cross join
(select #rank := 0, #magic_xp := NULL) params
order by magic_xp desc;
Note the complicated expression for the variables. The assignment of both variables is in a single expression. This is on purpose. MySQL does not guarantee the order of assignment of expressions in a SELECT, and sometimes, it does not even evaluate them in order. A single expression is the safe way to do this logic.
A more standard approach in SQL is to use a correlated subquery:
select t.*,
(select count(distinct t2.magic_xp)
from table t2
where t2.magic_xp >= t.magic_xp
) as rank
from table t;
query
set #i := 0;
set #lagxp := null;
select id, username, magic_xp,
#i := if(#lagxp = magic_xp, #i,
if(#lagxp := magic_xp, #i + 1, #i + 1)) as rank
from hs_users
order by magic_xp desc
;
or
SELECT id, username, magic_xp,
IF (#score=hs_users.magic_xp, #rank:=#rank, #rank:=#rank+1) as rank,
#score:=hs_users.magic_xp score
FROM hs_users, (SELECT #score:=0, #rank:=0) r
ORDER BY magic_xp DESC;
output
+-----+------------+----------+------+----------+
| id | username | magic_xp | rank | lagxp |
+-----+------------+----------+------+----------+
| 988 | Shapescape | 14926894 | 1 | 14926894 |
| 737 | Ozan | 13034431 | 2 | 13034431 |
| 989 | Kurt | 13034431 | 2 | 13034431 |
| 6 | Legacy | 0 | 3 | 0 |
+-----+------------+----------+------+----------+
sqlfiddle
Sound the solution :)
SELECT id, username, magic_xp,
IF (#score=hs_users.magic_xp, #rank:=#rank, #rank:=#rank+1) as rank,
#score:=hs_users.magic_xp score
FROM hs_users, (SELECT #score:=0, #rank:=0) r
ORDER BY magic_xp DESC;
Thanks to #amdixon
select
#rank:=if(magic_xp=#prev_magic_xp,#rank,#rank+1) as rank,
username,
magic_xp,
#prev_magic_xp:=magic_xp as prev_magic_xp
from user,(select #rank:=0,#prev_magic_xp="") t
order by magic_xp desc
For your reference: http://sqlfiddle.com/#!9/09bb3/2
I have a these tables
USERS
+----------+---------------+
| id_users | usr_email |
+----------+---------------+
| 1 | a#domain.com |
| 2 | b#domain.com |
| 3 | c#domain.com |
| 4 | d#domain.com |
| 5 | e#domain.com |
+----------+---------------+
RANKING
+-------------+-----------+----------+
| id_ranking | id_users | points |
+-------------+-----------+----------+
| 50 | 1 | 27 | //3rd
| 51 | 2 | 55 | //1st
| 52 | 3 | 9 | //5th
| 53 | 4 | 14 | //4th
| 54 | 5 | 38 | //2nd
+-------------+-----------+----------+
I would like to retireve user's data along with it's ranking position, filtering by e-mail. So for example if I want info for mail c#domain.com I should get
+----------+--------|---------------+
| id_users | points | rank_position |
+----------+--------|---------------+
| 3 | 9 | 5 |
+----------+--------|---------------+
I've found this piece of query that returns the ranking position
SELECT x.id_users, x.position
FROM (
SELECT t1.id_ranking, t1.id_users, #rownum := #rownum + 1 AS position
FROM ranking t1
JOIN (SELECT #rownum := 0) r ORDER BY t1.points desc
) x
WHERE x.id_users = 3
But I can't manage to use it in my old query
select u.*, r.points
from users u
left join ranking r on r.id_users = u.id_users
where u.usr_email = 'c#domain.com'
My attemp
select u.*, r.points, p.*
from users u
left join ranking r on r.id_users = u.id_users,
(SELECT x.id_users, x.position
FROM (
SELECT t1.id_ranking, t1.id_users, #rownum := #rownum + 1 AS position
FROM ranking t1
JOIN (SELECT #rownum := 0) r ORDER BY t1.points desc
) x
WHERE x.id_users = u.id_users) p
where u.usr_email = 'c#domain.com'
Any help?
You are missing a join condition. But also, the outer join to ranking is not necessary. You can "remember" the points in the subquery:
select u.*, r.points, r.position
from users u left join
(select r.*, #rownum := #rownum + 1 AS position
from ranking r CROSS JOIN
(SELECT #rownum := 0) r
order by r.points desc
) r
ON r.id_users = u.id_users
where u.usr_email = 'c#domain.com'
select u.id_users,
r.points,
count(ifnull(r2.id_users, 0)) + 1 as rank_position
from users u
join ranking r
on u.id_users = r.id_users
left join ranking r2
on r2.points > r.points
where u.usr_email = 'c#domain.com'
group by u.id_users, r.points
Fiddle: http://sqlfiddle.com/#!2/1444e8/1/0
Rather than a variable this counts the number of users who have more points than the given user, and then adds one. This is an equivalent way of calculating their rank, in terms of number of points.
My table
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | (null) |
| S2 | 55 | (null) |
| S3 | 56 | (null) |
| S4 | 55 | (null) |
| S5 | 52 | (null) |
| S6 | 51 | (null) |
| S7 | 53 | (null) |
+------+-------+--------+
Refer : http://www.sqlfiddle.com/#!2/5d046/1
I would like to add 3,2,1 points to the highest Marks. Here S3 goes to 3 points, S2,S4 goes to 2 points and S1,S7 goes to 1 points.
Final outputs looks,
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | 1 |
| S2 | 55 | 2 |
| S3 | 56 | 3 |
| S4 | 55 | 2 |
| S5 | 52 | 0 |
| S6 | 51 | 0 |
| S7 | 53 | 1 |
+------+-------+--------+
Plz help
My suggestion is that you first calculate the ranking of each mark, and then use that in a case statement in an update.
The following query shows one way to calculate the ranking:
select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc;
(As a note: I am a bit uncomfortable with this method, because MySQL does not guarantee the order of evaluation of the two expressions with constants. If #marks were set before #rn, then it wouldn't work. In practice, that does not seem to happen. And, this is more efficient that the equivalent with a correlated subquery.)
You can then put this into an update using join:
update myTable join
(select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc
) mr
on myTable.Name = mr.Name
set myTable.Points := (case when mr.ranking = 1 then 3
when mr.ranking = 2 then 2
when mr.ranking = 3 then 1
else 0
end);
This has been tested on your SQL Fiddle.
You can do it via variables (see samples in other answers), or via case:
select
myTable.*,
case
when max1.marks is not null then 3
when max2.marks is not null then 2
when max3.marks is not null then 1
else 0
end as score
from
myTable
LEFT JOIN
(select marks from myTable order by marks desc limit 1) AS max1
ON myTable.marks=max1.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 2,1) AS max2
ON myTable.marks=max2.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 3,1) AS max3
ON myTable.marks=max3.marks;
the demo can be found here.
UPDATE myTable t1
INNER JOIN
(
SELECT #row:=#row-1 AS RowPoints, Marks
FROM (
SELECT Marks
FROM myTable
GROUP BY Marks
ORDER BY Marks DESC
LIMIT 3
) AS TopMarks
INNER JOIN (SELECT #row:=4) AS RowInit
) AS AddPoints ON t1.Marks = AddPoints.Marks
SET Points = COALESCE(Points, 0) + AddPoints.RowPoints;
This should work just fine. You should and and index on the Marks column.
The simplest way to do this:
SELECT t.Name Name, t.Marks Marks,
(CASE WHEN Marks = (Select max(marks) from mytable) THEN 3 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 2) a) THEN 2 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 3) b) THEN 1 ELSE 0 END)
AS `Points`
FROM mytable t;
SQL Fiddle
I need an sql that will give the position of the student ranked in order of marks scored in a specific examtyp e.g. CAT! only.the sql below gives the position of the student but does not distinguish the examtyp.it ranks without considering the examtyp.
res_id admNo stream examtyp termId marks grade points year
1 2129 0 CAT1 1 525 C 62 2013
2 4093 0 CAT1 1 569 B+ 69 2013
3 2129 0 CAT2 1 550 B+ 67 2013
4 4093 0 CAT2 1 556 B+ 68 2013
6 2129 0 FINAL 1 559 B+ 68 2013
7 2129 0 AVERAGE 1 545 B 66 2013
7 4093 0 FINAL 1 581 B+ 70 2013
8 4093 0 AVERAGE 1 569 B+ 69 2013
$sql = "SELECT 1 + (SELECT count(*) FROM $table a
WHERE a.total_marks > b.total_marks ) AS rank
FROM $table b WHERE admNo=? AND examCategory=? AND termId=? AND year=?
ORDER BY rank LIMIT 1";
$res = $this->db->query($sql, array($admNo, $examCategory, $term, $year));
This should work for you:
SELECT res_ID,
admNo,
stream,
examtyp,
termId,
grade,
points,
`year`,
Position
FROM ( SELECT #r:= CASE WHEN #e = examtyp THEN #r + CASE WHEN #p = points THEN 0 ELSE #i END ELSE 1 END Position,
#i:= CASE WHEN #p = points THEN #i + 1 ELSE 1 END incr,
#e:= Examtyp,
#p:= points,
res_ID,
admNo,
stream,
examtyp,
termId,
grade,
points,
`year`
FROM T,
(SELECT #e:= '') e,
(SELECT #r:= 0) r,
(SELECT #p:= 0) p,
(SELECT #i:= 0) i
ORDER BY examtyp, points
) T
WHERE T.admNo = 4093
AND T.Examtyp = 'CAT1'
It uses the same principle of using variables that has been suggested, however also partitions by examtyp, resetting the position to 0 for each new exam type, it also records the previous points to deal with ties, so if 3 people get the same mark they all get the same position.
Example on SQL Fiddle
Note in the bottom pane of the fiddle the results for AVERAGE are equal so both get position = 1
Try the Query
SET #rank=0;
select
#rank := #rank+1 AS rank
result_id,
marks_scored,
admNo,
Aggregate_points,
year
from tale_name
order by marks_scored DESC
Try this query
Query 1:
select
#rn:=if(#prv=examtyp, #rn+1, 1) as rId,
admNo,
#prv:=examtyp as exmtyp,
marks
from table1
join
(select #rn:=0,#prv:='') tmp
order by exmtyp, marks desc
SQL FIDDLE:
| RID | ADMNO | EXMTYP | MARKS |
---------------------------------
| 1 | 4093 | AVERAGE | 569 |
| 2 | 2129 | AVERAGE | 545 |
| 1 | 4093 | CAT1 | 569 |
| 2 | 2129 | CAT1 | 525 |
| 1 | 4093 | CAT2 | 556 |
| 2 | 2129 | CAT2 | 550 |
| 1 | 4093 | FINAL | 581 |
| 2 | 2129 | FINAL | 559 |
EDIT
Query 1:
select * from (
select
#rn:= #rn+1 as rId,
admNo,
examtyp,
marks
from table1
join
(select #rn:=0) tmp
where examtyp='CAT1'
order by examtyp, marks desc
) tmp where tmp.admNo=2129
SQL FIDDLE:
| RID | ADMNO | EXAMTYP | MARKS |
---------------------------------
| 2 | 2129 | CAT1 | 525 |
try this -
SELECT q1.rownum
FROM
(
SELECT *, #rownum:=#rownum + 1 AS rownum
FROM $table t, (SELECT #rownum:=0) r
WHERE examtyp = 'CAT1'
ORDER BY marks
) q1
WHERE q1.admNo=?
2) Since you modified the requirement to get equal ranks for same marks, u might need to do something like this -
SELECT q1.rownum
FROM
(
SELECT *, #rownum:=#rownum + 1 AS rownum
FROM
(SELECT DISTINCT marks FROM table1 t WHERE t.examtyp = 'CAT1' ORDER BY t.marks) q2,
(SELECT #rownum:=0) r
) q1,
table1 t2
WHERE
t2.examtyp = 'CAT1'
AND t2.marks=q1.marks
AND t2.admNo=?;
Above, you need to change examCategory at two places.
This is not the most optimized query..but it will do ur work.
3) as per your third requirement to get incremented count of next student, this might do the trick -
SELECT ROWNUM
FROM
(
SELECT q1.marks, min(q1.rownum) AS rownum
FROM
(
SELECT t1.marks, #rownum:=#rownum + 1 AS rownum
FROM
table1 t1,
(SELECT #rownum:=0) r
WHERE
t1.examtyp='CAT1'
ORDER BY t1.marks asc
) q1
GROUP BY q1.marks
) q2,
table1 t2
WHERE
t2.examtyp = 'CAT1'
AND t2.marks=q2.marks;
AND t2.admNo=?;