Selecting sum of single column depending on two columns on same table - mysql

I have a table very similar to the one below. p1 and p2 on the table refer to id of player on an another table.
id score p1 p2 date
-- ----- -- -- ----
1 12 1 2 2011.10.21
2 23 3 4 2011.10.22
3 21 1 3 2011.10.23
4 35 5 1 2011.10.24
5 11 2 3 2011.10.25
What I want to do is the get the player id (p1 or p2) with highest score. My solution is something like select sum(score) but I can't form a query because a player may appear in both p1 or p2 columns.
Also a bigger problem is when I want to sort scores from highest to lowest. I dont know what to do. How can I sum and sort a score if I need to group to separate columns? The result I want is similar to this output:
pID score times_played
--- ----- ------------
1 68 3
3 55 3
5 35 1
2 23 2
4 23 1
Is my database design flawed? If there is a more intelligent way I'd like to know. Should I need seperate single queries so I can merge them on PHP or something?
Any help would be appreciated.
Cheers.
PS: I couldnt think a nice subject. Feel free to edit.

You can put the players in one column as so:
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
You now have players in one column. You can do this to get the score sum for all players
select sum(score), player from (
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
) group by player
Now, you say that you also want to know how many times the player played and sort them in descending order:
select sum(score), player, count(*) as timesPlayed from (
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
) group by player order by sum(score) desc

Try this to get players with highest score (disregarding ties)
select id,p1,p2
from table t1
join (select max(score) as MaxS) xx on xx.MaxS = t1.Score
limit 1
To get player total score, try this
select Player as pID,Sum(tot) as Score, count(*) as TimesPlayed
from
(
select p1 as Player,sum(score) as Tot
from table
group by p1
union all
select p2,sum(score)
from table
group by p2
) xx
Group by xx.Player
order by Score desc

Alternatively to using UNION (ALL) on the table, you could try something like this:
SELECT
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END AS pID,
SUM(t.score) AS score,
COUNT(*) AS times_played
FROM atable t
CROSS JOIN (SELECT 1 AS PlayerNumber UNION ALL SELECT 2) p
GROUP BY
pID /* this is probably MySQL-specific; most, if not all, other major
database systems would require repeating the entire pID expression here, i.e.
GROUP BY
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END
*/
ORDER BY
score DESC,
times_played DESC /* this is based on your result set;
you might want to omit it or change it to ASC */
UPDATE, in an answer to a question in the comments: joining the result set to the user table:
SELECT
`user`.*, /* you should probably specify
the necessary columns explicitly */
totals.score,
totals.times_played
FROM `user` u
INNER JOIN (
SELECT
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END AS pID,
SUM(t.score) AS score,
COUNT(*) AS times_played
FROM atable t
CROSS JOIN (SELECT 1 AS PlayerNumber UNION ALL SELECT 2) p
GROUP BY
pID
) totals ON user.id = totals.pID
ORDER BY
totals.score DESC,
totals.times_played DESC

Related

Exclude users with zero score from ranking

I've come across this great solution to rank users based on their score in mysql.
SELECT d.*, c.ranks
FROM
(
SELECT Score, #rank:=#rank+1 Ranks
FROM
(
SELECT DISTINCT Score
FROM tableName a
ORDER BY score DESC
) t, (SELECT #rank:= 0) r
) c
INNER JOIN tableName d
ON c.score = d.score
However, I would like to know if there is a way to exclude users with 0 or without score from the ranking, but still return these users in the results.
So for example
KEY username password score Ranks
1 Anna 123 8 1
2 Bobby 345 5 2
3 Helen 678 5 2
4 Jon 567 -2 3
5 Arthur ddd -9 4
4 Chris 444 0
5 Liz eee 0
Since you want to SELECT all of your users, start with that:
SELECT
user.*
FROM user
Now, we want to add in a table of ranked users, so we'll start to add in complexity. We're aiming to get a temporary table of ranked users, so we'll LEFT JOIN as to not filter out any non-ranked users.
SELECT
user.*,
ranked_user.score,
ranked_user.rank
FROM user
LEFT JOIN(
// subquery
) AS ranked_user ON ranked_user.user_id = user.id
Then we'll have to figure out the section for the subquery where the ranks are determined. You have most of it already, I'm just going to add in an IF statement to only assign a rank if they have a score. Altogether, you get this:
SELECT
user.*,
ranked_user.score,
ranked_user.rank
FROM user
LEFT JOIN(
SELECT
score,
user_id,
IF(score = 0 OR score IS NULL, null, #rank:=#rank+1) AS rank
FROM(
SELECT
DISTINCT score,
user_id
FROM stat
ORDER BY score DESC
) t, (SELECT #rank:= 0) r
) ranked_user ON ranked_user.user_id = user.user_id
Here's how that IF statement works:
IF(score = 0 OR score IS NULL,
# if c.score is 0 or missing
null,
# set the value to null
#rank:=#rank+1)
# otherwise, calculate a rank
AS rank
# call this value "rank"
Just a side note: I'd change user.* and actually list each column of user that you want. That's considered best practice.

mysql ranking based on sum of values

having a mysql table with multiple records belonging many different users like this:
id score
1 , 50
1 , 75
1 , 40
1, 20
2 , 85
2 , 60
2 , 20
i need to get the rank of each id but after finding the sum of their score;
the rank should be the same if the total score for each player is the same.
this gives me the total for each player:
select id,sum(score) as total from table_scores group by id order by total desc;
is it posssible to find the sum like above and use it to rank the players in one query?
Something big missing from the accepted answer. The rank needs to be bumped after a tie. If you've got 2 tied for 3rd place, there is no 4th place.
The following query is an adjustment of the accepted SQL to account for this and reset the rank variable (#r in the query) to match the row value. You can avoid the extra addition in the CASE/WHEN but initializing #row to 1 instead of 0 but then the row value is off by 1 and my OCD won't let that stand even if row number is not valuable.
select
id, total,
CASE WHEN #l=total THEN #r ELSE #r:=#row + 1 END as rank,
#l:=total,
#row:=#row + 1
FROM (
select
id, sum(score) as total
from
table_scores
group by
id
order by
total desc
) totals, (SELECT #r:=0, #row:=0, #l:=NULL) rank;
You can rank rows using variables:
select
id, total,
CASE WHEN #l=total THEN #r ELSE #r:=#r+1 END as rank,
#l:=total
FROM (
select
id, sum(score) as total
from
table_scores
group by
id
order by
total desc
) totals, (SELECT #r:=0, #l:=NULL) rank;
Please see it working here.
i find one more way to this problem... This one is based on JOIN clause
SET #rank = 0;
SELECT t1.id, t1.score, t2.rank
FROM (SELECT id, SUM(score) as score
FROM table_scores GROUP BY id ORDER BY score Desc) AS t1
INNER JOIN
(SELECT x.score, #rank:=#rank + 1 as rank FROM
(SELECT DISTINCT(SUM(score)) AS score
FROM table_scores
GROUP BY id ORDER BY score DESC) AS x) AS t2
ON t1.score = t2.score
Here is SQL Fiddle: http://sqlfiddle.com/#!9/2dcfc/16
P.S. it's interesting to see there is more then one way to solve a problem...

mysql query sorting and ordering

Say i have a table called "users" and it has 40 rows of records, each row has fields:
id, firstname, group_id, login_count, stay_on_page_count
groups has
administrator (1), manager (2), employee (3)
is it possible to create a query that will sort and order the rows this way
group _id stay_on_page_count login_count
========= ================== ===========
1 100mins 100
1 90mins 90
2 100mins 100
3 100mins 100
1 80mins 80
1 70mins 70
2 90mins 90
3 90mins 90
1 60mins 60
1 50mins 50
2 80mins 80
3 80mins 80
1 40mins 40
1 30mins 30
2 70mins 70
3 70mins 70
Basically I would like to create a 4x4 grid view using the query result. the pseudo code is probably
SELECT all FROM user table and to group the result in to cluster of 4,
while each 4 should have ORDER BY group_id ASC as first priority (1,1,2,3)
AND stay_on_page_count ORDER BY DESC as second priority,
AND login_count ORDER BY DESC as last or third priority
i don't know if the pseudo code explains enough, but that's the only thing i can came up with :)
And if its possible, then will it sacrifice performance?
Is there any better approach to accomplish this?
I am using Mysql and PHP (CakePHP 2.x)
Thanks
One approach (using summarised as a table holding the summarised values listed in the question):
select * from
(select s1.*,
#rank1:=#rank1+1 as rankcalc,
floor(#rank1/2) rankgroup,
#rank1%2 rankingroup
from (select #rank1:=1) r1
cross join
(select * from summarised where group_id=1 order by stay_on_page_count desc) s1
union all
select s2.*,
#rank2:=#rank2+1 as rankcalc,
#rank2 rankgroup,
2 rankingroup
from (select #rank2:=0) r2
cross join
(select * from summarised where group_id=2 order by stay_on_page_count desc) s2
union all
select s3.*,
#rank3:=#rank3+1 as rankcalc,
#rank3 rankgroup,
3 rankingroup
from (select #rank3:=0) r3
cross join
(select * from summarised where group_id=3 order by stay_on_page_count desc) s3
) sq order by rankgroup, rankingroup
SQLFiddle here.
Note that this solution is dependent on the ordering being evaluated in the sequence specified in the sub-queries, and not overridden by the optimizer - this should work in current versions of MySQL, but may not work in MariaDB (the open source fork of MySQL) or future versions of MySQL.
While this will generate the order you want it's very much a forced and hard coded effort. It makes several assumptions like login_count is always in the ranges you listed.
SELECT group_ID, Stay_on_Page_count, Login_Count, myset
FROM (
SELECT group_ID, Stay_on_Page_Count, Login_Count, 1 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count >= 90) OR (group_ID in (2,3) and Login_Count=100)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 2 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 70 and 80)
OR (group_ID in (2,3) and Login_Count=90)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 3 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 50 and 60)
OR (group_ID in (2,3) and Login_Count=80)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 4 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 30 and 40)
OR (group_ID in (2,3) and Login_Count=70))
ORDER BY myset, group_ID, Login_count Desc

Order MySQL table by two columns of equal importance

I have a two player game which stores scores in a table.
I want to select high scores from the table, this is my current code:
SELECT * FROM games
ORDER BY GREATEST(player1Score,player2Score) DESC
LIMIT 10
The problem is it only returns one instance of each row, even if for example the lower of the two scores in row 1 warrants inclusion in the top 10.
Use UNION ALL:
SELECT col1, col2, col3, player1Score AS score FROM games
UNION ALL
SELECT col1, col2, col3, player2Score AS score FROM games
ORDER BY score DESC
LIMIT 10
Also, don't use SELECT *. List the columns explicitly.
( SELECT *, player1Score as score
FROM games
ORDER BY score DESC
LIMIT 10
)
UNION ALL
( SELECT *, player2Score AS score
FROM games
ORDER BY score DESC
LIMIT 10
)
ORDER BY score DESC
LIMIT 10
You would probably be better off creating a players table and a join table (even though there are only two users). Then you could easily create a query to do what you're trying to do. Of course, it will take a little bit to alter your update/save functions to match the new schema.
players
-----
playerId
playerName
joinPlayerGame
-----
joinId
playerId
gameId
games
-----
modify the player score fields to just be 'score'
SELECT g.*, p.playerName FROM players p INNER JOIN joinPlayerGame j ON j.playerId = p.playerId INNER JOIN games g ON g.<whatever your key is> = j.gameId ORDER BY g.score DESC LIMIT 10
I hope that helps, good luck!

Get User Rank Based on Sum with Pagination

There are several rank posts out there but I have yet to see one dealing with when the results are paginated and when the ranking criteria (in this case: points) is equal to the previous user. I have tried a few of the pre-existing examples but none have worked.
I have a table called "users" with the column "id". I also have a table called "points" with the columns "user_id" and "amount".
I need:
1.) Users with duplicate sum of points to have the same rank
Points Table
user_id amount
1 10
2 20
1 5
3 20
3 -5
4 5
Rank should be
rank user_id total
1 2 20
2 1 15
2 3 15
3 4 5
2.) Needs to maintain the ranking from one page to another so the rank has to be gathered in the query and not the resulting PHP.
3.) Display ALL users not just ones with rows in the points table because some users have 0 points and I want to display them last.
Right now I'm just listing the users in order of their points but their rank is not gathered because it wasn't working.
$getfanspoints = mysql_query("SELECT DISTINCT id,
(SELECT SUM(amount) AS points FROM points WHERE points.user_id = users.id) AS points
FROM users
ORDER BY points DESC LIMIT $offset, $fans_limit", $conn);
I've read these solutions and none have worked.
[Roland's Blog][1]
[How to get rank based on SUM's][2]
[MySQL, get users rank][3]
[How to get rank using mysql query][4]
and a few others whose link I can't find right now.
Any suggestions?
[EDIT]
I used ypercube's bottom answer.
SELECT COUNT(*) AS rank
, t.user_id
, t.total
FROM
( SELECT user_id
, SUM(amount) AS total
FROM points
GROUP BY user_id
) AS t
JOIN
( SELECT DISTINCT
SUM(amount) AS total
FROM points
GROUP BY user_id
) AS dt
ON
t.total <= dt.total
GROUP BY t.user_id
ORDER BY rank
, user_id
But the above may be really slow with a big table and points awarded often. It might be really better to have just this and calculate the ranks in your application code:
SELECT users.id AS user_id
, SUM(amount) AS total
FROM
users
LEFT JOIN
points
ON points.user_id = users.id
GROUP BY users.id
ORDER BY total DESC
, user_id
This will work, too (edited, to work with the users table and with OFFSET):
SELECT *
FROM
( SELECT
#rank := #rank + (#t <> total) AS rank
, user_id
, #t := total AS total
FROM
( SELECT users.id AS user_id
, COALESCE(SUM(amount),0) AS total
FROM users
LEFT JOIN points
ON users.id = points.user_id
GROUP BY users.id
) AS o
CROSS JOIN
( SELECT #rank := 0, #t := -999999
) AS dummy
ORDER BY total DESC
, user_id
) tmp
LIMIT x OFFSET y