Question

I have the following table:

CREATE TABLE `score` (  
    `score_id` int(10) unsigned NOT NULL auto_increment,  
    `user_id` int(10) unsigned NOT NULL,  
    `game_id` int(10) unsigned NOT NULL,  
    `thescore` bigint(20) unsigned NOT NULL,  
    `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,  
    PRIMARY KEY  (`score_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;  

That's a score table the stores the user_id and game_id and score of each game. there are trophies for the first 3 places of each game. I have a user_id and I would like to check if that specific user got any trophies from any of the games.

Can I somehow create this query without creating a temporary table ?

Was it helpful?

Solution

SELECT s1.*
FROM score s1 LEFT OUTER JOIN score s2 
 ON (s1.game_id = s2.game_id AND s1.thescore < s2.thescore)
GROUP BY s1.score_id
HAVING COUNT(*) < 3;

This query returns the rows for all winning games. Although ties are included; if the scores are 10,16,16,16,18 then there are four winners: 16,16,16,18. I'm not sure how you handle that. You need some way to resolve ties in the join condition.

For example, if ties are resolved by the earlier game winning, then you could modify the query this way:

SELECT s1.*
FROM score s1 LEFT OUTER JOIN score s2 
 ON (s1.game_id = s2.game_id AND (s1.thescore < s2.thescore
     OR s1.thescore = s2.thescore AND s1.score_id < s2.score_id))
GROUP BY s1.score_id
HAVING COUNT(*) < 3;

You could also use the timestamp column to resolve ties, if you can depend on it being UNIQUE.

However, MySQL tends to create a temporary table for this kind of query anyway. Here's the output of EXPLAIN for this query:

+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
|  1 | SIMPLE      | s1    | ALL  | NULL          | NULL | NULL    | NULL |    9 | Using temporary; Using filesort | 
|  1 | SIMPLE      | s2    | ALL  | PRIMARY       | NULL | NULL    | NULL |    9 |                                 | 
+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+

OTHER TIPS

SELECT game_id, user_id
FROM score score1  
WHERE (SELECT COUNT(*) FROM score score2  
       WHERE score1.game_id = score2.game_id AND score2.thescore > score1.thescore) < 3   
ORDER BY game_id ASC, thescore DESC;

A clearer way to do it, and semitested.

SELECT DISTINCT user_id
FROM
(
    select s.user_id, s.game_id, s.thescore,
    (SELECT count(1)
    from scores
    where game_id = s.game_id
        AND thescore > s.thescore  
    ) AS acount FROM scores s
) AS a

WHERE acount < 3

Didn´t test it, but should work fine:

SELECT
  *,
  @position := @position + 1 AS position
FROM
  score
  JOIN (SELECT @position := 0) p
WHERE
  user_id = <INSERT_USER_ID>
  AND game_id = <INSERT_GAME_ID>
ORDER BY
  the_score

There you can check the position field to see if it´s between 1 and 3.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top