简体   繁体   中英

Selecting a count of rows having a max value

Working example: http://sqlfiddle.com/#!9/80995/20

I have three tables, a user table, a user_group table, and a link table.

The link table contains the dates that users were added to user groups. I need a query that returns the count of users currently in each group. The most recent date determines the group that the user is currently in.

SELECT
  user_groups.name, 
  COUNT(l.name) AS ct, 
  GROUP_CONCAT(l.`name` separator  ", ") AS members 
FROM user_groups
  LEFT JOIN 
    (SELECT MAX(added), group_id, name FROM link LEFT JOIN users ON users.id = link.user_id GROUP BY user_id) l 
    ON l.group_id = user_groups.id
GROUP BY user_groups.id

My question is if the query I have written could be optimized, or written better.

Thanks! Ben

You actual query is not giving you the answer you want; at least, as far as I understand your question. John actually joined group 2 on 2017-01-05, yet it appears on group 1 (that he joined on 2017-01-01) on your results. Note also you're missing one Group 4 .


Using standard SQL, I think the next query is what you're looking for. The comments in the query should clarify what each part is doing:

SELECT
    user_groups.name AS group_name, 
    COUNT(u.name) AS member_count, 
    group_concat(u.name separator ', ') AS members 
FROM 
    user_groups
    LEFT JOIN
    (
       SELECT * FROM 
       (-- For each user, find most recent date s/he got into a group
       SELECT 
           user_id AS the_user_id, MAX(added) AS last_added
       FROM 
           link 
       GROUP BY 
           the_user_id
       ) AS u_a
       -- Join back to the link table, so that the `group_id` can be retrieved
       JOIN link l2 ON l2.user_id = u_a.the_user_id AND l2.added = u_a.last_added
    ) AS most_recent_group ON most_recent_group.group_id = user_groups.id

    -- And get the users...
    LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY 
   user_groups.id, user_groups.name
ORDER BY
   user_groups.name ;

This can be written in a more compact way in MySQL (abusing the fact that, in older versions of MySQL, it doesn't follow the SQL standard for the GROUP BY restrictions).

That's what you'll get:

group_name | member_count | members       
:--------- | -----------: | :-------------
Group 1    |            2 | Mikie, Dominic
Group 2    |            2 | John, Paddy   
Group 3    |            0 | null          
Group 4    |            1 | Nellie

dbfiddle here


Note that this query can be simplified if you use a database with window functions (such as MariaDB 10.2). Then, you can use:

SELECT
    user_groups.name AS group_name, 
    COUNT(u.name) AS member_count, 
    group_concat(u.name separator ', ') AS members 
FROM 
    user_groups
    LEFT JOIN
    (
        SELECT 
            user_id AS the_user_id, 
            last_value(group_id) OVER (PARTITION BY user_id ORDER BY added) AS group_id
        FROM
            link
        GROUP BY
            user_id
    ) AS most_recent_group ON most_recent_group.group_id = user_groups.id

    -- And get the users...
    LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY 
   user_groups.id, user_groups.name
ORDER BY
   user_groups.name ;

dbfiddle here

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