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

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

Related

Trying to get the 2 min(count()) with Ties

I allow myself to write a thread regarding a query I'm trying to make for hours now. I'm trying to get the name of the friend (friend.first_name) who refuses the most proposed dates for events.
To do this I'm counting the number of proposed date and ORDER BY ASC.
SELECT COUNT(*) 'NbrProposedDate', f.FIRST_NAME,
f.LAST_NAME, f.FRIEND_ID
FROM PROPOSES
NATURAL JOIN FRIEND f
GROUP BY f.FRIEND_ID
ORDER BY NbrProposedDate ASC
LIMIT 1;
However, this does not take into account TIES.
What I'm looking for is the following result :
Furthermore, I've seen something with FETCH FIRST 1 ROW WITH TIES, however it does not seem to work with MySQL (getting SQL syntax issue).
Finally, I've had found an alternative using a function :
-- Find Minimum Count
SELECT MIN(cnt) INTO #min FROM (SELECT COUNT(*) cnt FROM PROPOSES NATURAL JOIN FRIEND f GROUP BY f.FRIEND_ID) t;
-- Show Friends with minimum count
DROP VIEW IF EXISTS troublemaker;
CREATE VIEW troublemaker AS
SELECT FIRST_NAME, LAST_NAME
FROM PROPOSES p
JOIN (SELECT FRIEND.FRIEND_ID
FROM PROPOSES
NATURAL JOIN FRIEND
GROUP BY FRIEND.FRIEND_ID
HAVING COUNT(*) = #min) t
ON p.FRIEND_ID = t.FRIEND_ID
JOIN FRIEND ON t.FRIEND_ID = FRIEND.FRIEND_ID
ORDER BY p.FRIEND_ID ASC;
However, the issue is that, I need to put this into a view, but "View's SELECT contains a variable or parameter".
Therefore, I'm looking for another alternative or a solution to fix this issue.
P.S. : here is an MLD :
View is not required, a query result could be used as subquery
SELECT
COUNT(*) NbrProposedDate,
MAX(f.FIRST_NAME) FIRST_NAME,
MAX(f.LAST_NAME) LAST_NAME,
f.FRIEND_ID
FROM PROPOSES
NATURAL JOIN FRIEND f
GROUP BY f.FRIEND_ID
HAVING COUNT(*) = (
SELECT COUNT(*)
FROM PROPOSES
NATURAL JOIN FRIEND f
GROUP BY f.FRIEND_ID
ORDER BY COUNT(*)
LIMIT 1
)
On MySQL 8+, it can also use RANK()
WITH ranks AS (
SELECT
COUNT(*) NbrProposedDate,
MAX(f.FIRST_NAME) FIRST_NAME,
MAX(f.LAST_NAME) LAST_NAME,
f.FRIEND_ID,
RANK() OVER (ORDER BY COUNT(*) ASC) rk
FROM PROPOSES
NATURAL JOIN FRIEND f
GROUP BY f.FRIEND_ID
)
SELECT * FROM ranks WHERE rk = 1

MYSQL COUNT with SELECT condition

Im newbie please help me on my project. I have 2 tables below.
Table user:
Table Likes
I want to count all likes per story_id and check if the given user has like the story else it will return null.
as of now this is my query and output, given user id = 1.
SELECT COUNT(*) , sl.story_id, u.id as user
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id AND sl.user_id = 1
GROUP BY sl.story_id
My output:
But what i want to get output is:
Given: user_id = 1
Given user_id = 4
Sorry for the construction of my question i dont know how. Thanks in advance
If the query should display the result for a one particular user_id only then try the following query:
select count(*),
story_id,
(case find_in_set(1, Group_concat(user_id separator ',')) >0 then 1
else NULL
end
)as user_id
from Stories_Likes
group by story_id;
In above query, you can put user_id manually after when, or you can set a variable with particular Id and use it in the query.
For i.e., if you want to check for user_id=4, then put 4 after when and then.
Click here for the Updated Demo
Hope it helps!
The users table is left joined to the query, so it may have null values. Instead, you should use the user_id column from the stories_likes table:
SELECT COUNT(*) , sl.story_id, sl.user_id as user
-- Here --------------------------^
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id AND sl.user_id = 1
GROUP BY sl.story_id
Got the answer. for those who has the same problem and needed this here's the answer.
SELECT COUNT(*) , sl.story_id, t.user_id
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id
LEFT JOIN (SELECT * FROM stories_likes WHERE user_id = 4) t ON
sl.story_id = t.story_id
GROUP BY sl.story_id
Where the given user is declared on temporary table t

SQL intermediate table having column = max(column)

I have 2 tables: user and review, a one-to-many relationship.
When I execute the following query:
SELECT
user_id,
count(*) totalReviews,
USER . NAME
FROM
review,
USER
WHERE
USER .id = review.user_id
GROUP BY
user_id
I get:
1 2 marius
2 2 daniela
3 1 alin
What I want to do now is to display first 2 users because they have given the most reviews(2).
I tried adding having, if I hardcode having totalReviews=2 it works, but if I write having total = max(total) I get 0 results, while if I'm trying with,
SELECT
*
FROM
(
SELECT
user_id,
count(*) total,
USER . NAME
FROM
review,
USER
WHERE
USER .id = review.user_id
GROUP BY
user_id
) A
WHERE
total = (SELECT max(total) FROM A) `
I get an error (table A doesn't exist)
You would do this with ORDER BY and LIMIT:
SELECT u.id, count(*) as totalReviews, u.name
FROM review r JOIN
user u
ON u.id = r.user_id
GROUP BY u.id, u.name
ORDER BY totalReviews DESC
LIMIT 2;
Notes:
Never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
Table aliases make the query easier to write and read.
EDIT:
If occurs to me that you want all users with the maximum number of reviews, not exactly 2. Here is one method:
SELECT u.id, COUNT(*) as totalReviews, u.name
FROM review r JOIN
user u
ON u.id = r.user_id
GROUP BY u.id, u.name
HAVING totalReviews = (SELECT COUNT(*)
FROM review r2
GROUP BY r2.user_id
ORDER BY COUNT(*) DESC
LIMIT 1
);
Note that the subquery in the HAVING clause is simpler than the outer query. There is no need to bring in the user name.

Comparing counts in a SQL where clause

I have two tables: users and photos.
Users have many photos. Photos have a column called user_id, photos have one user. Photos also have a column called reported which is 0 or 1.
I need to know the number of users who have at least 1 photo with reported = 1. I also need to get the number of users who have at least 2 photos with reported = 1.
How would I do this? Here's what I'd like to do, but it obviously doesn't work:
select count(*)
from users join
(select * from photos where photos.reported = 1) as p2
on users.photo_id = p2.id;
This is at least 1
select count(distinct userid)
from photos
where reported = 1
This is at least 2.
select count(distinct userid)
from photos
where reported = 1
group by userid
having count(userid) > 2
Just get a histogram of the counts:
select numreported, count(*), min(user_id), max(user_id)
from (select p.user_id, sum(p.reported = 1) as numreported
from photos p
group by p.user_id
) p
group by numreported
order by numreported;
This gives you the number of users that have all counts of numreported, including 0.
Something like the following should work
select count(hasOne) cntHasOne, count(hasTwo) cntHasTwo from
(select users.user_id, 1 hasOne,
case when count(*) > 1 then 1 else 0 end hasTwo
from users inner join solution on(users.user_id = solution.user_id)
where solution.winning_status = 1
group by user_id) T1

MYSQL Toplist by the sum of teammember points

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