MYSQL Toplist by the sum of teammember points - mysql

So i would like to make a toplist in mysql.
I have two table, users and teams.
In users I have: id,name,points,teamid
In teams I have: t_id,teamname,leaderid
I would like to make a toplist. The 10 teams with the most points.
I hope it is understandable what I want.

You need to calculate the total sum of points by using the subquery which will give you the top 10 points then join this result with a query which has team information with the sum of points for each team,from this approach if there is tie in scores of 2 teams they will be included in top 10 teams regarding the points
select t2.* from
(select t.*,sum(u.points) allpoints
from teams t
join users u
on(t.t_id= u.teamid)
group by t.t_id
) t2
join
(select sum(points) allpoints
from users
group by teamid
order by allpoints desc
limit 10) tp
on(t2.allpoints = tp.allpoints )
if you don't care for tie scenario you can simply use limit and order by
select t.*,sum(u.points) allpoints
from teams t
join users u
on(t.t_id= u.teamid)
group by t.t_id
order by allpoints desc
limit 10

Related

Show MAX value with a join

I'm struggling a little with a query I'm trying to build. For a game I want to display the top 10 of scores.
My table looks like this:
player = id, playername, username
score = id, track, car, bestscore, totalscore, player_id (foreign key to player.id)
For the top10 I want to show the playername, the total score, the car and the track.
The current query I have is:
SELECT p.playername, MAX(s.totalscore) totalscore, s.car, s.track
FROM player p
INNER JOIN score s on p.id = s.player_id
GROUP BY p.playername
ORDER BY MAX(s.totalscore) DESC
LIMIT 10
This seems to work fine, except for one problem. If a user has a score of 50 on track 1 with car 1, and then puts a score of 60 on track 1 with car 2, I see car 1 on the query. It does not seem to get the according car of the top score if this user.
I hope it makes sense what I just told.
Thanks in advance!
EDIT:
SELECT p.playername, MAX(s.totalscore) as totalscore, s.car, s.track
FROM (SELECT * FROM score ORDER BY totalscore DESC) s
INNER JOIN player p ON s.player_id = p.id
GROUP BY p.playername
ORDER BY MAX(s.totalscore) DESC
LIMIT 10
This query seems to do the trick. I've been trying around and the result-set is exactly what I mean. Is it any good though? I'm not good at SQL and just because it works doesn't always mean it's good, does it?
When you have a non-aggregated field as part of an aggregated query that isn't in your group by, and has a many to one relationship with the relation on which you are grouping, MySQL gives no guarantee what it will return.
This query would throw an error in another RDBMS. If you care about the top score by (person,car) tuple, just add it to the group by. However, if you want each player to have a max score, regardless of what car was used, you'll need a subquery or a self join.
One way to do it:
SELECT p.playername, s1.totalscore , s1.car, s1.track
FROM player p
INNER JOIN
score s1 on p.id = s1.player_id
WHERE s1.totalscore = ( SELECT MAX(totalscore) FROM score s2 WHERE s2.player_id=s1.player_id)
ORDER BY s1.totalscore DESC
LIMIT 10
You use "Group By" on playername so the other columns will group in "what mysql desides" manner. If you want to get the cars you should add - group by car.
SELECT p.playername, MAX(s.totalscore) totalscore, s.car, s.track
FROM player p
INNER JOIN score s on p.id = s.player_id
GROUP BY p.playername, s.car //added car
ORDER BY s.totalscore DESC
LIMIT 10

calculating player statistics from database

i have the following tables.
i want to fetch all the player statistics record from the given tables, the records of individual player includes.
Player Name
Position
Total Number of games played
Number of goals scored
Total number of assist for goals.
Total Points (total goals + total assist = total points).
after trying i came up with this query
SELECT SQL_CALC_FOUND_ROWS
CONCAT(u.first_name, ' ', u.last_name) as player_name,
p.position,
COUNT(g.id)
FROM
gce_player p
LEFT JOIN
gce_user u ON(u.id = p.user_id)
LEFT JOIN
gce_game_team_lineup gtl ON(gtl.player_id = p.id)
LEFT JOIN
gce_game_team gt ON(gt.id = gtl.game_team_id)
LEFT JOIN
gce_game_goal gg ON(gg.player_id = p.id)
LEFT JOIN
gce_game g ON(g.id = gt.game_id)
GROUP BY p.id
ORDER BY p.id asc
the above query returns me proper record till total number of games played, i am facing issue fetching the proper records after this, ill appreciate any kind of help on this.
here is the link to sqlfiddle if you want to look at the schema, i have added some test data too.
thank you.
UPDATE :
here are few of the rules to remember.
Number of goals scored = total number of goals scored by a player. for example if in gce_game_goal table there are 10 rows which have
the value of player_id as 4 it means the player have scored 10 goals
and i need to fetch this record for individual player, and
likewise if there are 7 rows in which player_id have value of 3 this
means player with id 3 have scored 7 goals and likewise.
Total number of assist for goals = total number of assist given to a goalie by a player (assist is like a pass in football). i need to
calculate total number of assist or pass that was done by a user.
for each goal there will be two assist, and each assist are players
who pass the ball to a golaie. i want to count the number of passes
or assist given by a player. for example if in gce_game_goal
table there are 8 rows or records that have the value of 3 in either
assis1_id or assist2_id column, this means player with id 3 have
scored 8 assist in total
.
kindly let me know if you still have any doubts/question, ill try to improve my question
Thanks
The problem that you are facing is caused by aggregating along multiple different dimensions of the data (say by game and by goal). This results in a cross product for each player.
A fairly general solution is to do aggregations in the from clause, along each dimension. Each variable (or perhaps a few variables) comes from a different aggregation:
select u.last_name, u.first_name, p.position,
pg.goals, pg.assists, (pg.goals + pg.assists) as TotalPoints
from gce_player p join
gce_user u
on p.user_id = u.id left outer join
(select player_id, SUM(goal) as goals, SUM(assist) as assists
from ((select player_id, 1 as goal, 0 as assist
from gce_game_goal
) union all
(select assist1_id, 0 as goal, 1 as assist
from gce_game_goal
) union all
(select assist2_id, 0 as goal, 1 as assist
from gce_game_goal
)
) t
group by player_id
) pg
on pg.player_id = p.id left outer join
(select gtl.player_id, count(*) as NumTeams
from gce_game_team_lineup gtl join
gce_game_team gt
on gtl.id = gt.team_id
) g
on g.player_id = p.id
Try this
SELECT
CONCAT(u.first_name, ' ', u.last_name) as player_name,
count(g.id) as Goals,
(select
count(*)
from
gce_game_goal
where
assist1_id = p.player_id)
+(select
count(*)
from
gce_game_goal
where
assist2_id = p.player_id) as Assists,
count(g.id)
+ (select
count(*)
from
gce_game_goal
where
assist1_id = p.player_id)
+ (select
count(*)
from
gce_game_goal
where
assist2_id = p.player_id) as Total
FROM
gce_player as p
LEFT JOIN
gce_game_goal as g ON p.id = g.player_id
LEFT JOIN
gce_user u ON(u.id =p.user_id)
GROUP BY p.player_id

Ordering JOIN results when grouping

Take the below for example:
SELECT
*
FROM
auctions AS a
LEFT JOIN winners AS w ON a.auction_id=w.auction_id
WHERE
a.active=1
AND
a.approved=1
GROUP BY
a.auction_id
ORDER BY
a.start_time DESC
LIMIT
0, 10;
Sometimes this may match multiple results in the winners table; I don't need both of them, however I want to have control over which row I get if there are multiple matches. How can I do an ORDER BY on the winners table so that I can make sure the row I want is the first one?
It is difficult to accurately answer without seeing your table structure but if your winners table has a winner date column or something similar, then you can use an aggregate function to get the first record.
Then you can return the record with that earliest date similar to this:
SELECT *
FROM auctions AS a
LEFT JOIN winners w1
ON a.auction_id=w1.auction_id
LEFT JOIN
(
select auction_id, min(winner_date) MinDate -- replace this with the date column in winners
from winners
group by auction_id
) AS w2
ON a.auction_id=w2.auction_id
and w1.winner_date = w2.MinDate
WHERE a.active=1
AND a.approved=1
ORDER BY a.start_time DESC
SELECT *
FROM auctions AS a
LEFT JOIN (select auction_id from winners order BY auction_id limit 1) AS w ON a.auction_id = w.auction_id
WHERE a.active = 1
AND a.approved = 1
GROUP BY a.auction_id
ORDER BY a.start_time DESC
Change the reference to the winners table in the join clause to a sub-query. This then gives you control over the number of records returned, and in what order.

Left join, how to specify 1 result from the right?

This one is fairly specific, so I'm hoping for a quick fix.
I have a single result in my leaderboard table for each team. In my teams table, I have several results for each team (one result per game to enable team development history).
I want to show each team in the leaderboard once, and have teamID replaced by strName. Problem is, my left join is giving me one record for each team result; I just want a single record.
SELECT * , a.strName AS teamName
FROM bb_leaderboards l
LEFT JOIN bb_teams a ON ( l.teamID = a.ID )
WHERE l.season =8
AND l.division =1
ORDER BY l.division DESC , points DESC , wins DESC , l.TDdiff DESC
LIMIT 0 , 30
What do I need to do to this to get a 1:1 output?
You could do a SELECT DISTINCT instead, but you'll have to narrow down your select a bit. So:
SELECT DISTINCT l.*, a.strName AS teamName
...
That should filter out the duplicates.

querying for user's ranking in one-to-many tables

I am trying to write a query to find the score rank of a user's games. I need it to take in a user id and then return that user's relative ranking to other user's scores. There is a user and a game table. The game table has a userId field with a one-to-many relationship.
Sample table:
users:
id freebee
1 10
2 13
games:
userId score
1 15
1 20
2 10
1 15
passing $id 1 into this function should return the value 1, as user 1 currently has the highest score. Likewise, user 2 would return 2.
Currently this is what I have:
SELECT outerU.id, (
SELECT COUNT( * )
FROM users userI, games gameI
WHERE userI.id = gameI.userId
AND userO.id = gameO.userId
AND (
userI.freebee + SUM(gameI.score)
) >= ( userO.freebee + SUM(gameO.score) )
) AS rank
FROM users userO,
games gameO
WHERE id = $id
Which is giving me an "invalid use of group function" error. Any ideas?
SELECT u.id,total_score,
( SELECT COUNT(*) FROM
(SELECT u1.id, (IFNULL(u1.freebee,0)+ IFNULL(SUM(score),0)) as total_score
FROM users u1
LEFT JOIN games g ON (g.userId = u1.id)
GROUP BY u1.id
)x1
WHERE x1.total_score > x.total_score
)+1 as rank,
( SELECT COUNT(DISTINCT total_score) FROM
(SELECT u1.id, (IFNULL(u1.freebee,0)+ IFNULL(SUM(score),0)) as total_score
FROM users u1
LEFT JOIN games g ON (g.userId_Id = u1.id)
GROUP BY u1.id
)x1
WHERE x1.total_score > x.total_score
)+1 as dns_rank
FROM users u
LEFT JOIN
( SELECT u1.id, (IFNULL(u1.freebee,0)+ IFNULL(SUM(score),0)) as total_score
FROM users u1
LEFT JOIN games g ON (g.userId = u1.id)
GROUP BY u1.id
)x ON (x.id = u.id)
rank - (normal rank - e.g. - 1,2,2,4,5), dns_rank - dense rank (1,2,2,3,4). Column total_score - just for debugging...
The query does not like the reference of an outer table in the Sum function SUM(gameO.score) in the correlated subquery. Second, stop using the comma format for joins. Instead use the ANSI syntax of JOIN. For example, in your outer query did you really mean to use a cross join? That is how you wrote and how I represented it in the solution below but I doubt that is what you want.
EDIT
I've adjusted my query given your new information.
Select U.id, U.freebee, GameRanks.Score, GameRanks.Rank
From users As U
Join (
Select G.userid, G.score
, (
Select Count(*)
From Games As G2
Where G2.userid = G.userid
And G2.Score > G.Score
) + 1 As Rank
From Games As G
) As GameRanks
On GameRanks.userid = U.id
Where U.id =1
I'm not a MySQL person, but I believe that the usual way to do ranking in it is using a variable within your SQL statement. Something like the below (untested):
SELECT
SQ.user_id,
#rank:=#rank + 1 AS rank
FROM
(
SELECT
U.user_id,
U.freebee + SUM(COALESCE(G.score, 0)) AS total_score
FROM
Users U
LEFT OUTER JOIN Games G ON
G.user_id = U.user_id
) SQ
ORDER BY
SQ.total_score DESC
You could use that as a subquery to get the rank for a single user, although performance-wise that might not be the best route.
Here is "simplified" version for calculating a rank based only on "games" table. For calculating rank for a specific game only you need to add additional joins.
SELECT COUNT(*) + 1 AS rank
FROM (SELECT userid,
SUM(score) AS total
FROM games
GROUP BY userid
ORDER BY total DESC) AS gamescore
WHERE gamescore.total > (SELECT SUM(score)
FROM games
WHERE userid = 1)
It's based on the idea that ranking == number of players with bigger score + 1
Check this out:
http://rpbouman.blogspot.com/2009/09/mysql-another-ranking-trick.html