简体   繁体   中英

MySQL distinct with group by

Im working on a system where players can ask questions. Each player is assigned to a different game and language. They also get bonus points for this.

I need a report that will show stats about how many questions were asked, how many are still active, how many are closed and how much total bonuses were given. The report is per game and question type.

The query below is working OK, but now i have to remove the duplicated players, so that only one question per player counts (the last one he asked).

SELECT 
q.game, 
q.language, 
q.questionType, 
COUNT(q.player) AS total, 
SUM(IF(q.status = 'Active', 1, 0)) AS active, 
SUM(IF(q.status = 'Closed', 1, 0)) AS closed, 
SUM(q.points) AS points
FROM questions AS q
LEFT JOIN players AS p ON p.id = q.asked_by 
LEFT JOIN games AS g ON g.id = p.game 
GROUP BY q.game, q.questionType

As ChrisLondon points out, you don't need most of the tables. A more efficient version of that query is:

SELECT q.game, q.language, q.questionType, 
       COUNT(q.player) AS total, 
       SUM(q.status = 'Active') AS active, 
       SUM(q.status = 'Closed') AS closed, 
       SUM(q.points) AS points
FROM questions q left outer join
     (select player, max(id) as maxid
      from questions
       group by player
     ) qp
     on q.id = qp.maxid  /* don't need the player here because the ids are unique */
GROUP BY q.game, q.language, q.questionType;

Many versions of MySQL (pre-5.6) do a horrible job of optimizing the in subquery. The subquery is re-run (and perhaps even re-compiled) for every row processed. Moving it to the from clause fixes this problem.

This is also assuming that the phrase "only one question per player counts (the last one he asked)" applies to all the quantities in the query. Because it is the last per player , you have the risk that some games may not have any rows, because all the players have newer questions. If you want the last question for each game, then change the subquery to:

SELECT q.game, q.language, q.questionType, 
       COUNT(q.player) AS total, 
       SUM(q.status = 'Active') AS active, 
       SUM(q.status = 'Closed') AS closed, 
       SUM(q.points) AS points
FROM questions q left outer join
     (select game, max(id) as maxid
      from questions
       group by game
     ) qg
     on q.id = qg.maxid  /* don't need the player here because the ids are unique */
GROUP BY q.game, q.language, q.questionType;

First off you're not using data from the games table or the players table so there's no need to join them.

SELECT 
    q.game, 
    q.language, 
    q.questionType, 
    COUNT(q.player) AS total, 
    SUM(IF(q.status = 'Active', 1, 0)) AS active, 
    SUM(IF(q.status = 'Closed', 1, 0)) AS closed, 
    SUM(q.points) AS points
FROM questions AS q
WHERE id IN (SELECT MAX(id) FROM questions GROUP BY player)
GROUP BY q.game, q.questionType

What I'm doing is selecting the last unique question per player and then running the query on it. You don't have q.language in your GROUP BY and it's not an aggregate function. It's not recommended you do that. It's more recommended that you include language in your group as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM