Get max paired users scores with min time sums - mysql

I have MySQL tables that look like this
users
user_id | partner_id | name
--------+------------+-----
1 | 2 | aaa
2 | 1 | bbb
3 | 4 | ccc
4 | 3 | ddd
games
game_id | user_id
--------+--------
1 | 1
2 | 1
3 | 2
4 | 3
5 | 4
6 | 4
scores
game_id | level | score | time
--------+-------+-------+-----
1 | 1 | 1 | 10
1 | 2 | 1 | 10
1 | 3 | 1 | 10
2 | 1 | 0 | 20
2 | 2 | 0 | 20
2 | 3 | 0 | 20
3 | 1 | 1 | 30
3 | 2 | 1 | 30
3 | 3 | 1 | 30
4 | 1 | 1 | 2
4 | 2 | 1 | 2
4 | 3 | 1 | 2
5 | 1 | 1 | 5
5 | 2 | 1 | 5
5 | 3 | 1 | 5
6 | 1 | 1 | 3
6 | 2 | 1 | 3
6 | 3 | 0 | 3
And i need to query it so it sums points and time per game, so it looks like this
game_id | user_id | sumPoints | sumTime
--------+---------+-----------+--------
1 | 1 | 3 | 30
2 | 1 | 0 | 60
3 | 2 | 3 | 90
4 | 3 | 3 | 6
5 | 4 | 3 | 15
6 | 4 | 2 | 9
And then i need to get best scores per pair (where it takes better score of one user), so it looks like this:
user1_id | user2_id | sumPoints | sumTime
---------+----------+-----------+--------
3 | 4 | 3 | 6
1 | 2 | 3 | 30
That's the final result. I'd really appreciate if someone could show me how it should looks like as sql query.
I'd like to mention that first part is solved by JW 웃 in this post
Thanks in advance.

Something like this should work (this answers your second query)
SELECT
user_details.user_id,
user_details.partner_id,
score_details.score,
score_details.time
FROM
( SELECT
min(user_id) as user_id,
max(user_id) as partner_id
FROM
users
GROUP BY
user_id + partner_id ) AS user_details
JOIN
( SELECT
scores.game_id ,
games.user_id,
sum(score) score,
sum(time) time,
#row_num := IF(#prev_value=games.user_id,#row_num+1,1) AS row_num,
#prev_value := games.user_id
FROM
scores
inner join games on games.game_id = scores.game_id
inner join users on users.user_id = games.user_id
GROUP BY
scores.game_id
ORDER BY
user_id,
score
) as score_details ON ( score_details.user_id = user_details.user_id AND score_details.row_num = 1)
The first part of JOIN gets the users along with their partners, users appearing first within their pair are displayed first, eg: if there are 2 users with ID 1 and 2 I consider the user_id of user 1 as he appears first within his pair.
The second query is based on "echo_me" answer along with a row_number that specifies the ranking of the scores for each user, the highest score has the rank as 1 for every user.
SQLFIDDLE
Hope this is helpful

try this
select scores.game_id ,games.user_id,sum(score) score, sum(time) time
from scores
inner join games
on games.game_id = scores.game_id
inner join users
on users.user_id = games.user_id
group by scores.game_id
DEMO HERE
for the best score
select users.user_id as user1_id,users.partner_id as user2_id,sum(score) score, sum(time) time
from scores
inner join games
on games.game_id = scores.game_id
inner join users
on users.user_id = games.user_id
group by scores.game_id
order by sum(time) asc limit 1
DEMO HERE
OUTPUT.
USER1_ID USER2_ID SCORE TIME
1 2 3 30

Related

How to get an entry's position from a complex query using MySQL?

I want to know a player's position (rank) from a leaderboard. I'm not having a leaderboard table, it's being generated from the users table and a query. This is how I get the entries:
SELECT
u.id as userId,
u.xp as xp,
u.nickname as nickname,
SUM(opr.games_won) as wonGames,
SUM(opr.games_lost) as lostGames
FROM
users u
INNER JOIN
opponent_player_result opr ON u.id = opr.user_id
WHERE
u.last_login >= 1548374400000
AND u.xp > 0
AND u.zone_id = 1
GROUP BY
u.id
ORDER BY
u.xp DESC,
wonGames DESC,
lostGames ASC
LIMIT 0, 50;
The xp column is for storing the player's experience points. After every game play this column in updated.
The opponent_player_result is a separated table where I keep the player's played game result:
+----+------------+-----------+-----------------+---------+
| id | games_lost | games_won | opponent_player | user_id |
+----+------------+-----------+-----------------+---------+
| 1 | 3 | 0 | 0 | 1 |
| 2 | 2 | 1 | 1 | 1 |
| 3 | 0 | 3 | 2 | 1 |
| 4 | 4 | 2 | 0 | 2 |
| 5 | 0 | 1 | 1 | 2 |
| 6 | 1 | 1 | 2 | 2 |
| 7 | 2 | 3 | 0 | 3 |
| 8 | 3 | 0 | 1 | 3 |
| 9 | 3 | 4 | 2 | 3 |
+----+------------+-----------+-----------------+---------+
As you may have noticed, each player are having 3 opponent player result (because there are only 3 bots which the player can play).
The above query would give me the following result:
+--------+----+----------+----------+-----------+
| userId | xp | nickname | wonGames | lostGames |
+--------+----+----------+----------+-----------+
| 1 | 34 | nick1 | 4 | 5 |
| 3 | 29 | nick3 | 7 | 8 |
| 2 | 29 | nick2 | 4 | 5 |
+--------+----+----------+----------+-----------+
I'm not using MySQL RANK() functions, neither I'm not incrementing variables to associate the players positions (maybe that's what I should do).
How to get a single player's position from the above leaderboard result?
For example, user with #id 3 would have rank 2.
This is the query that I'm working on to find a player's position within the leaderboard:
SELECT u.id AS userId,
u.xp AS xp,
1 +
(SELECT COUNT(*)
FROM
(SELECT SUM(opr.games_won) AS wonGames,
SUM(opr.games_lost) AS lostGames
FROM users AS p
INNER JOIN opponent_player_result AS opr ON p.id = opr.user_id
WHERE p.last_login >= 1548374400000
AND p.xp > 0
AND p.xp > u.xp
AND p.zone_id = 1
GROUP BY p.id
ORDER BY p.xp DESC, wonGames DESC, lostGames ASC) AS counter) AS ranking
FROM users AS u
WHERE u.id = 1;
But I'm getting the following error:
ERROR 1054 (42S22): Unknown column 'u.xp' in 'where clause'
As far I know, MySQL allows only 1 level deep nested or subquery, but in my example I am 3 level deep. But how do I reference then from the subquery to the outer query?
Is this the right approach to get the leaderboard? Should I create a separated table and update it periodically? I am very afraid of performance hit.
I'm using MySQL 8.0 version.
This is straightforward using ranking functions of MySQL 8.0. For example, DENSE_RANK computes the ranking of each row within a partition (tied records are assigned the same rank).
SELECT
u.id as userId,
u.xp as xp,
u.nickname as nickname,
SUM(opr.games_won) as wonGames,
SUM(opr.games_lost) as lostGames,
DENSE_RANK() OVER(ORDER BY u.xp DESC) rnk
FROM
users u
INNER JOIN
opponent_player_result opr ON u.id = opr.user_id
WHERE
u.last_login >= 1548374400000
AND u.xp > 0
AND u.zone_id = 1
GROUP BY
u.id, u.xp, u.nickname
ORDER BY
u.xp DESC,
wonGames DESC,
lostGames ASC
LIMIT 0, 50;
Demo on DB Fiddle :
| userId | xp | nickname | wonGames | lostGames | rnk |
| ------ | --- | -------- | -------- | --------- | --- |
| 1 | 34 | nick1 | 4 | 5 | 1 |
| 3 | 29 | nick3 | 7 | 8 | 2 |
| 2 | 29 | nick2 | 4 | 5 | 2 |

MySql Query - Finding out the Intersect

I have 3 tables, user_tag, article_tag, article_ignored. I want to fetch only those articles of user_id = 48 for which at least one article tags matches with user tag. I am stuck in this since long time and don't have any idea how to achieve this.
The table structure is as follows:
article_ignored table
id | user_id
1 | 48
2 | 48
3 | 48
article_tag table
id | article_id | tag_id
1 | 1 | 1
2 | 1 | 5
3 | 1 | 7
4 | 2 | 2
5 | 2 | 8
6 | 3 | 3
7 | 3 | 2
user_tag table
id | user_id | tag_id
1 | 48 | 2
2 | 48 | 3
Required output:
article_ignored
id
2
3
You can use NOT EXISTS:
SELECT id, user_id
FROM article_ignored AS ai
WHERE EXISTS (SELECT 1
FROM article_tag AS at
JOIN user_tag AS ut ON at.tag_id = ut.tag_id
WHERE ai.id = at.article_id)
Demo here

double JOIN and GROUP on hasMany associations

I have 3 models (tables):
Presentation hasMany PresentationView hasMany SlideView
Fields:
Presentation: id, title
PresentationView: id, presentation_id
SlideView: id, presentation_view_id, duration
I need a query to get statistics for each presentation. Statistics are:
number of PresentationView per each Presentation
total duration of all SlideView.duration from slide views that belong to Presentation (through PresentationView)
So basically it seems like double JOIN and double GROUP but the joins doesn't work for me - I tried every combination of LEFT/INNER/RIGHT double joins and I can't make it work. The best I had it was that Presentation had grouped PresentationView but duration was SUMed just from SlideViews that belonged to just one PresentationViews not all for Presentation...
I would like to avoid nested SELECTs if possible. just JOIN/GROUP
The first thing is a simple JOIN and COUNT:
SELECT p.id, COUNT(*)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
GROUP BY p.id
The second one has to use a SUM (and JOIN):
SELECT p.id, SUM(s.duration)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
JOIN SlideView s ON v.id = s.presentation_view_id
GROUP BY p.id
If you want both in a single query:
SELECT p.id, SUM(s.duration), COUNT(DISTINCT v.id)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
JOIN SlideView s ON v.id = s.presentation_view_id
GROUP BY p.id
Reason for DISTINCT:
Tables:
Presentation: PresentationView: SlideView:
p.id | title v.id | presentation_id s.id | presentation_view_id | duration
1 | abc 1 | 1 1 | 1 | 100
2 | xyz 2 | 1 2 | 1 | 150
3 | 1 3 | 2 | 200
4 | 1 4 | 2 | 250
5 | 1 5 | 3 | 300
6 | 2 6 | 3 | 400
7 | 2 7 | 4 | 500
8 | 5 | 600
9 | 6 | 100
10 | 6 | 200
11 | 7 | 350
Example result set BEFORE the group:
p.id | v.id | s.id | s.duration
-------------------------------
1 | 1 | 1 | 100
1 | 1 | 2 | 150
1 | 2 | 3 | 200
1 | 2 | 4 | 250
1 | 3 | 5 | 300
1 | 3 | 6 | 400
1 | 4 | 7 | 500
1 | 5 | 8 | 600
2 | 6 | 9 | 100
2 | 6 | 10 | 200
2 | 7 | 11 | 350
AFTER the group without distinct:
p.id | SUM | COUNT
------------------
1 | 8 | 2500
2 | 3 | 650
With distinct:
p.id | SUM | COUNT
------------------
1 | 5 | 2500
2 | 2 | 650
In mean time I found answer:
SELECT presentations.title, SUM(slide_views.duration), COUNT(DISTINCT presentation_views.id)
FROM presentations
LEFT JOIN presentation_views ON presentations.id = presentation_views.presentation_id
LEFT JOIN slide_views ON presentation_views.id = slide_views.presentation_view_id
GROUP BY presentations.id

Get all actions of the last-three users for each post

In a last Question, i asked about geting all actions of the last three users from a history table that stores all actions done by users on deferments posts, now what i want is to get the same thing but for each post.
all actions of donne by the last-three users for each posts
history table
id | post_id | action | user_id
1 | 5 | 1 | 3
1 | 23 | 2 | 1
2 | 24 | 2 | 6
3 | 34 | 1 | 7
4 | 35 | 1 | 1
5 | 36 | 1 | 1
6 | 23 | 2 | 3
7 | 24 | 2 | 1
8 | 23 | 1 | 4
9 | 24 | 1 | 5
10 | 24 | 1 | 1
11 | 23 | 1 | 2
12 | 23 | 4 | 1
thanks and sorry if it seem to be a duplicate post
I think this will work:
SELECT a.user_ID, a.post_id, a.action
FROM tableName a
INNER JOIN
(
SELECT DISTINCT
#curRow:=IF(#prevRow=post_Id,#curRow+1,1) rn,
user_ID,
Post_Id,
#prevRow:=Post_Id
FROM (
SELECT DISTINCT Post_Id, User_Id
FROM TableName
ORDER BY Post_Id, ID DESC
) t
JOIN (SELECT #curRow:= 0) r
) b ON a.post_id = b.post_id AND a.user_id = b.user_id
WHERE b.rn <= 3
ORDER BY a.post_id, a.User_ID
And the Fiddle.
Coudl this be what you are looking for?
SQLFiddle
Code:
SELECT a.user_ID,
group_concat(a.post_id),
group_concat(a.action)
FROM tableName a
INNER JOIN
(
SELECT DISTINCT user_ID
FROM tableName
ORDER BY ID DESC
LIMIT 3
) b ON a.user_ID = b.user_ID
group by a.user_id
ORDER BY a.User_ID;
| USER_ID | GROUP_CONCAT(A.POST_ID) | GROUP_CONCAT(A.ACTION) |
--------------------------------------------------------------
| 2 | 7 | 3 |
| 3 | 5,5,4 | 1,2,5 |
| 6 | 7 | 2 |

Get all actions of the last three users for each post

In a last Question, i asked about geting all actions of the last three users from a history table that stores all actions done by users on deferments posts, now what i want is to get the same thing but for each post.
all actions of the last three users for each posts
history table
id | post_id | action | user_id
1 | 5 | 1 | 3
1 | 23 | 2 | 1
2 | 24 | 2 | 6
3 | 34 | 1 | 7
4 | 35 | 1 | 1
5 | 36 | 1 | 1
6 | 23 | 2 | 3
7 | 24 | 2 | 1
8 | 23 | 1 | 4
9 | 24 | 1 | 5
10 | 24 | 1 | 1
11 | 23 | 1 | 2
12 | 23 | 4 | 1
thanks and sorry if it seem to be a duplicate post
This is a query that requires lots of self joins:
select hl.post_id, h.*
from history h join
(select h.*, count(*) as NumLater
from history h join
history h2
on h.post_id = h2.post_id and
h2.id >= h.id
group by h.id
having NumLater <= 3
) hl
on h.user_id = hl.user_id
order by hl.post_id
The inner query does a self join to calculate the number of history entries after each record in the same post. The join then joins this by user_id to the history table. This version does not eliminate duplicates. So, a user could be in the last-three set for two different posts.