How to get rank in MySQL from 2 tables? - mysql

I have 2 different tables in my database by the name of: rank, settings.
Here is how each table looks like with a few records in them:
Table #rank:
id points userid
-- ----- ------
1 500 1
2 300 2
3 900 3
4 1500 4
5 100 5
6 700 6
7 230 7
8 350 8
9 850 9
10 150 10
Table #settings:
userid active
------ ------
1 0
2 1
3 1
4 1
5 1
6 0
7 1
8 1
9 0
10 1
I want to get the rank of a specific user by user_id from the rank table ordering by their points. Also I would Only want to include the users in the ranking results, if they have active = 1 set in the settings table.
I have a simple ranking query, but it is not really effective, because it does include everyone even if the user is not active:
SELECT * FROM
(SELECT #sort:=#sort+1 AS sort, points, userid
FROM rank,
(SELECT #sort := 0) s
ORDER BY points DESC) t
WHERE userid= 8
Any idea, how could I achieve my goals here?

Few sub queries. First gets all the users who are active in the right order. That is used as a source for another query to add the rank. Then this is used as the source for the points and rank for the userid you are actually interested in
SELECT sort, points
FROM
(
SELECT #sort:=#sort + 1 AS sort, points, userid
FROM
(
SELECT rank.points, rank.userid
FROM rank
INNER JOIN settings
ON rank.userid = settings.userid
WHERE settings.active = 1
ORDER BY points DESC
) sub0
CROSS JOIN (SELECT #sort:=0) sub2
) sub1
WHERE sub1.userid = 8

Borrowing the idea from: https://stackoverflow.com/a/4474389/92063
SELECT
#rn:=#rn+1 AS RANK
,USER_ID
,POINTS
FROM (
SELECT
R.userid AS USER_ID
,R.points AS POINTS
FROM
rank R
INNER JOIN
settings S
ON R.userid = S.userid
WHERE
S.active = 1
ORDER BY
R.points DESC
) t1, (SELECT #rn:=0) t2;

Related

MySQL Leaderboard Table

I'm trying to figure out how to Select a specific number of rows from a MySQL table based on WHERE clause. I have a table with 10 dummy users, I want to get 2 previous and 2 next users of specific user with their ranks.
user_id | points
==================
10 200
4 130
2 540
13 230
15 900
11 300
3 600
17 110
20 140
1 430
5 800
I achieved adding a column for ranking like:
user_id | points | rank
===========================
15 900 1
5 800 2
3 600 3
2 540 4
1 430 5
11 300 6
13 230 7
10 200 8
20 140 9
4 130 10
17 110 11
But the problem is that I want only 5 rows. Suppose I'm retrieving data for user with user_id = 11. The output should look like this:
user_id | points | rank
===========================
2 540 4
1 430 5
11 300 6
13 230 7
10 200 8
where user_id = 11 is in the centre with 2 rows above and 2 below. I have tried nesting UNIONS and SELECT statements but nothing seems to work properly.
Here's a suggestion if you're on MySQL 8+:
WITH cte AS (
SELECT user_id, points,
ROW_NUMBER() OVER (ORDER BY points DESC) AS Rnk
FROM mytable)
SELECT cte2.user_id,
cte2.points,
cte2.Rnk
FROM cte cte1
JOIN cte cte2
ON cte1.user_id=11
AND cte2.Rnk >= cte1.Rnk-2
AND cte2.Rnk <= cte1.Rnk+2
Using common table expression (cte) then do a self join with condition of user_id=11 as base to get the Rnk value of -2 and +2.
Demo fiddle
Since you're on older MySQL version, here's what I can suggest:
SET #uid := 11;
SET #Rnk := (SELECT Rnk
FROM
(SELECT user_id, points,
#r := #r+1 AS Rnk
FROM mytable
CROSS JOIN (SELECT #r := 0) r
ORDER BY points DESC) v
WHERE user_id = #uid);
SELECT user_id, points, Rnk
FROM
(SELECT user_id, points,
#r := #r+1 AS Rnk
FROM mytable
CROSS JOIN (SELECT #r := 0) r
ORDER BY points DESC) v
WHERE Rnk >= #Rnk-2
AND Rnk <= #Rnk+2;
If you will only use user_id as base, then the only part here you need to change is the SET #uid. The remaining queries are just fulfilling your condition of getting two positions above and below the rank retrieved according to the user_id. The base query in SET #Rnk is the same as the base query for the last one. The idea is to assign #Rnk variable with Rnk position of user_id=11 then use it in WHERE condition for the last query.
I'm not aware if there's any online fiddle still using MySQL 5.1 but here's probably the closest version to it, MySQL 5.5 demo fiddle.

Select multiple tables with only unique users and ordered by latest id

I have 2 tables, first one is called members:
id name show
1 John 1
2 Wil 1
3 George 1
4 Chris 1
Second is called score:
id user_id score
1 1 90
2 1 70
3 2 55
4 3 30
5 3 40
6 3 100
7 4 30
user_id from score is the id of members.
What I want is to show a scorelist with unique members.id, ordered by score.score and order by the latest score.id.
I use the following code:
SELECT members.id, members.show, score.id, score.user_id, score.score FROM members
INNER JOIN score ON score.user_id = members.id
WHERE members.show = '1'
GROUP BY score.user_id
ORDER BY score.score DESC, score.id DESC
The output is not ordered by the latest score.id, but it does show only unique user_id's:
id user_id score
1 1 90
3 2 55
4 3 30
7 4 30
It should be like:
id user_id score
6 3 100
2 1 70
3 2 55
7 4 30
I hope you can help me
You could use:
with cte as (
select id,
user_id,
score,
row_number() over(partition by user_id order by id desc) as row_num
from score
) select cte.id,user_id,score
from cte
inner join members m on cte.user_id=m.id
where row_num=1
order by score desc;
Demo
If your MySQL server doesn't support windows function, use:
select s.id,s.user_id,s.score
from score s
inner join members m on s.user_id=m.id
where s.id in (select max(id) as id
from score
group by user_id
)
order by score desc;
Demo

Max & AVG MYSQL INNER JOIN for 2 tables

I have 2 tables (example):
users:
ID company_ID
1 7
2 6
3 7
activity_rewards:
user_ID points activity_type_ID
1 1 7
1 2 7
1 1 7
1 1 8
2 1 7
2 1 7
2 2 8
2 1 7
3 2 7
3 1 7
3 2 8
3 1 8
(There are also tables for company and activity_types, but they shouldn't be relevant here)
I need a MYSQL query that will sum the total points for each user WHERE all users have a certain company_ID and for a certain activity_type_ID AND it will return the MAX and the AVG of the sum of all the users points
I have for example:
SELECT SUM(activity_rewards.points) AS totalpoints,
MAX(activity_rewards.points) AS maxpoints,
AVG(activity_rewards.points) AS avgpoints
FROM activity_rewards
INNER JOIN users
ON activity_rewards.user_ID = users.ID
WHERE ( (users.company_ID = "7") && (activity_rewards.activity_type_ID LIKE '8') )
In the example query only 3 results are involved. They are:
user_ID points activity_type_ID
1 1 8
3 2 8
3 1 8
I want to get:
user 1 has 1 point
user 3 has 3 points
Max is 3 average is 2
instead I'm getting Max is 2 and average is 1.33
The results are in line with your query, but your query is not in line with your requirements.
Your query calculates the max and the average of points across the relevant records. But you seem to want the max and the average of the points summed by user id.
This means that you need to calculate the sum of points per user in a subquery and then calculate the max and average in the outer query.
SELECT SUM(sumpoints) as totalpoints, max(sumpoints) as maxpoints, avg(sumpoints) as avgpoints
FROM
(SELECT users.ID, SUM(activity_rewards.points) AS sumpoints
FROM activity_rewards
INNER JOIN users ON activity_rewards.user_ID = users.ID
WHERE users.company_ID = 7 and activity_rewards.activity_type_ID = 8
GROUP BY users.ID) t
Do it like this:
SELECT MAX(sum_points) max, AVG(sum_points) avg, SUM(sum_points) sum_all FROM (SELECT SUM(t2.points) sum_points FROM users t1 JOIN activity_rewards t2 ON (t1.ID = t2.user_ID) WHERE ( (t1.company_ID = "7") AND (t2.activity_type_ID = '8') ) GROUP by t1.ID) as summation

getting last interaction from 3 tables

I have 3 tables that look somewhat like this:
tbl_rating_yes
ID, rater, rated, ratetype, mry_timestamp
1 1234 3456 3 '2015-06-01 23:01:00:000'
2 1234 7666 3 '2013-06-01 23:02:00:000'
3 5678 1234 3 '2013-06-01 23:14:00:000'
4 9085 4567 3 '2013-06-01 23:19:00:000'
tbl_rating_no
ID, rater, rated, ratetype, mrn_timestamp
1 6679 1234 1 '2015-06-01 13:09:00:000'
2 1234 9900 1 '2015-06-01 13:10:00:000'
3 8891 1111 1 '2015-06-01 14:56:00:000'
4 0010 4545 1 '2015-06-01 15:06:00:000'
tbl_rating_maybe
ID, rater, rated, ratetype, mrm_timestamp
1 5632 9000 2 '2013-06-01 11:00:59:000'
2 3421 8787 2 '2013-06-01 12:44:00:000'
3 0100 9887 2 '2013-06-01 16:04:00:000'
4 9085 1234 2 '2013-06-02 01:22:00:000'
i'd like to be able to find out the last interaction that rater 9085 did.
currently i have a query like:
SELECT GREATEST(IFNULL(`mrm_timestamp`,'1900-01-01 01:01:01'),
IFNULL(`mrn_timestamp`,'1900-01-01 01:01:01'),
IFNULL(`mry_timestamp`,'1900-01-01 01:01:01')) as timestamp
FROM tbl_rating_no rn
LEFT JOIN tbl_rating_yes ry
ON ry.`rater` = rn.`rater`
LEFT JOIN tbl_rating_maybe rm
ON rm.`rater` = rn.`rater`
WHERE rn.rater = 9085
ORDER BY timestamp
but since 9085 doesn't exist in the no rating table, it doesn't bring back a result. I'd like to be able to bring back the timestamp, the ratetype and the id at the least. there shouldn't be clashes but if there are bring back all the results for the max time. so this should ideally bring back the information from tbl_rating_maybe.
Use union all and limit:
select r.*
from ((select r.*
from tbl_rating_yes r
where r.rater = 9085
order by mry_timestamp desc
limit 1
)
union all
(select r.*
from tbl_rating_no r
where r.rater = 9085
order by mrn_timestamp desc
limit 1
)
union all
(select r.*
from tbl_rating_maybe r
where r.rater = 9085
order by mrm_timestamp desc
limit 1
)
) r
order by mry_timestamp desc
limit 1;
In fact, having multiple tables with essentially the same structure is typically a bad idea. You should consider restructuring your tables by combining these three into a single table, with a column containing the rating.

MySQL ranking query

Query:
SELECT u.user_id,r.totalVotes votes,r.totalPoints rating,#row:=#row+1 rank
FROM mismatch_user u
LEFT JOIN ratingItems r ON u.user_id=r.uniqueName,
(SELECT #row:=0) pos
ORDER BY votes DESC,rating DESC
Output:
user_id votes rating rank
2 2 10 2
6 2 9 6
3 2 5 3
1 1 5 1
4 1 5 4
27 1 5 27
9 0 0 9
The ranking miserably not telling me the truth and it's basing on the user_id. Can anyone help me?
Does this help?
select r.*, #row:=#row+1 rank
from
(SELECT u.user_id,r.totalVotes votes,r.totalPoints rating
FROM mismatch_user u
LEFT JOIN ratingItems r ON u.user_id=r.uniqueName
) r
join (SELECT #row:=0) pos
ORDER BY r.votes DESC, r.rating DESC
You're generating a sequence and that can lead to tricky behavior.
If your query is correct, the safest way to sort by rank is now to embed it in another SELECT:
SELECT *
FROM (
SELECT u.user_id,r.totalVotes votes,r.totalPoints rating,#row:=#row+1 rank
FROM mismatch_user u
LEFT JOIN ratingItems r ON u.user_id=r.uniqueName,
(SELECT #row:=0) pos
ORDER BY votes DESC,rating DESC) T
ORDER BY rank;