DucDigital
DucDigital

Reputation: 4622

How can I make this query?

This is the table for testing

----------------------------------------------
id | username | point | level | created_date
----------------------------------------------
 1 | name_a   |  1    |   1   | 2011-08-01
 2 | name_a   |  2    |   2   | 2011-08-02
 3 | name_b   |  5    |   1   | 2011-08-02
 3 | name_c   |  6    |   1   | 2011-08-02
 4 | name_d   |  1    |   1   | 2011-08-01
 5 | name_d   |  3    |   1   | 2011-08-02
 5 | name_d   |  5    |   2   | 2011-08-03
 4 | name_e   |  5    |   1   | 2011-08-01
 5 | name_e   |  5    |   2   | 2011-08-02
 5 | name_e   |  5    |   3   | 2011-08-03
----------------------------------------------

Requirement for the query is to query (as much as possible in one query) the username, point of the table.

  1. Order by sum of user's score on each level.
  2. If user have 2 score on the same level, it will only get the latest score.
  3. Group by username
  4. total score must be less than 10
  5. maximum of the score per level is 5

Output sample:

--------------------
 username  | tpoint|
--------------------
  name_d   |  8    |
  name_b   |  5    |
  name_a   |  3    |
--------------------

name_e and name_c was ignored.

Upvotes: 4

Views: 136

Answers (4)

Kees de Boer
Kees de Boer

Reputation: 21

SELECT 
    Query2.username 
  , Sum(Query2.SomVanpoint) AS point 
FROM 
    (SELECT 
            test.username 
          , test.level 
          , Sum(test.point) AS SomVanpoint 
        FROM 
            test 
        INNER 
        JOIN 
            (SELECT 
                    test.username 
                  , test.level 
                  , Max(test.created_date) AS MaxVancreated_date 
                FROM 
                    test 
                GROUP 
                    BY test.username 
                  , test.level
            ) AS Query1 
            ON 
            (test.username         = Query1.username) 
            AND (test.level        = Query1.level) 
            AND (test.created_date = Query1.MaxVancreated_date) 
        GROUP 
            BY test.username 
          , test.level 
        HAVING 
            (((Sum(test.point))<= 5))
    ) AS Query2 
GROUP 
    BY Query2.username 
HAVING 
    (((Sum(Query2.SomVanpoint))< 10)) 
ORDER 
    BY Sum(Query2.SomVanpoint) DESC; 

=== output:

username  | point
----------+------
name_d    |  8
name_b    |  5
name_a    |  3

Upvotes: 2

Jacob
Jacob

Reputation: 43219

SELECT SUM(t3.point) AS tpoint, t3.username
FROM (
    SELECT t1.level, t1.username, t1.created_date, t1.point
    FROM testing AS t1
    INNER JOIN (SELECT level, username, MAX(created_date) AS MaxDate
                FROM testing) AS t2
          ON (t1.level=t2.level AND t1.username=t2.username AND t1.created_date = t2.MaxDate)
    WHERE t1.point <= 5
    ) AS t3
GROUP BY t3.username
HAVING tpoint < 10
ORDER BY tpoint DESC

Don't know if I used the aliases correctly, hope this works!

The inner query with the join is to get the latest username,level combination where single level point count is > 5. This is then used to get the total sum per username and discard those with more then 10 points.

Upvotes: 2

symcbean
symcbean

Reputation: 48357

OK, taking part 2 first....

SELECT *
FROM table a
WHERE NOT EXISTS (
   SELECT 1
   FROM table b
   WHERE b.username=a.username
   AND a.created_date>b.created_date
)

But mysql doesn't cope with push-predicates very well, hence the max-concat trick, but that's going to make the query very complicated - worth revisiting if you've got performance problems. Now to add in the other stuff....parts 1,3 and 5

SELECT username, level, SUM(point)
FROM 
(SELECT *
   FROM table a
   WHERE NOT EXISTS (
      SELECT 1
      FROM table b
      WHERE b.username=a.username
      AND a.created_date>b.created_date
   )
) ilv
GROUP BY username, level
HAVING SUM(point) <= 5;

How you implement 4 depends on the exact sequence in which this constraint is applied in relation to the other constraints (particularly 2 and 5). The following should give the disred output from the stated input...

SELECT username, level, SUM(point)
FROM 
(SELECT *
   FROM table a
   WHERE NOT EXISTS (
      SELECT 1
      FROM table b
      WHERE b.username=a.username
      AND a.created_date>b.created_date
   )
) ilv,
(SELECT username, SUM(point) as totpoint
  FROM table c
  GROUP BY username
  HAVING SUM(point)<=10) ilv2
WHERE ilv.username=ilv2.username
GROUP BY username, level
HAVING SUM(point) <= 5;

Whoops - just read this again and saw that you're not interested to see a level breakdown in the output set - in which case Robin's answer is better.

Upvotes: 1

Robin Castlin
Robin Castlin

Reputation: 10996

Sounds like a fun query!

SELECT username, SUM(point) AS points
FROM (SELECT username, level, point
      FROM (SELECT username, level, LEAST(point, 5) AS point
            FROM table
            WHERE points <= 5
            ORDER BY created_date DESC) AS h
      GROUP BY username, level) AS h2
GROUP BY username
HAVING points < 10
ORDER BY points DESC

This should do it! Just replace "table".

EDIT:

Do you want to exclude rows which got a score over 5, or have the value as 5? Just remove WHERE points <= 5 if such.

Upvotes: 7

Related Questions