通过查看成员的最新状态获取成员列表

Get list of members by looking at the their latest status

我有一个复杂的案例,我正在将其简化为 MVCE 示例。假设我们有一个 MySQL table 包含一堆俱乐部和许多学生。每个学生可以加入多个社团,也可以随时离开。

出于 MVCE 目的,让我们考虑下面的 table:

/* Table: membership */
Club    Student     Date        Status
ABC     Alice       1/1/2020    Joined
ABC     Bob         1/1/2020    Joined
ABC     Charlie     1/2/2020    Joined
XYZ     Charlie     1/2/2020    Joined
ABC     Alice       1/6/2020    Left
ABC     Alice       1/12/2020   Joined
ABC     Charlie     1/12/2020   Left
XYZ     Alice       1/12/2020   Joined

我使用什么语句来查找 1/9/2020 俱乐部 ABC 中的所有学生?答案应该是 CharlieBob。如果我们将日期更改为 1/12/2020,答案应该是 AliceBob,因为那天 Alice 加入并且 Charlie 离开。

这是我尝试过的方法,但 return 我想要的是:

SELECT Student
FROM membership
WHERE
Club = "ABC" AND
(SELECT MAX(Date) WHERE Status="Joined" ) > (select MAX(Date) WHERE Status="Left") ;

此外,以上考虑了当前的成员资格。我怎样才能让它成为特定日期的会员?

试试这个:

SELECT
    Student
FROM
    Membership
WHERE
    Club = "ABC" AND Date = "2020-1-12";

如果您能将“加入”的记录与相应的“左”或null(如果有none)相匹配,那么您就大功告成了。我要应用的逻辑是对应于“加入”的记录必须由相同的“俱乐部”和“学生”记录。日期有点棘手,因为有可能出现这种情况:

Club    Student     Date          Status
ABC     Alice       2020-01-01    Joined
ABC     Alice       2020-01-02    Left
ABC     Alice       2020-01-03    Joined
ABC     Alice       2020-01-04    Left
ABC     Alice       2020-01-05    Joined

可以通过日期大于“加入”但最早的“左”记录找到对应的记录。

由于“加入”并不总是有相应的记录,如上例中最后一个“加入”有 none,我使用了使用 LEFT JOIN:

SELECT joined.club,
       joined.student,
       joined.date AS joindate,
       left_.date  AS leavedate
  FROM Membership AS joined
  LEFT JOIN Membership AS left_ ON joined.club = left_.club         -- must match
                               AND joined.student = left_.student   -- must match
                               AND left_.Status = 'Left'            -- match to "Left"
                               -- Only the minimum date of the dates
                               -- ..greater or equal to "Joined" date
                               AND left_.date = (SELECT min(date)   
                                                   FROM Membership min
                                                  WHERE 1 = 1
                                                    AND min.club = left_.club
                                                    AND min.student = left_.student
                                                    AND min.date >= joined.date 
                                                    AND min.Status = 'Left')
 WHERE joined.Status = 'Joined'

导致:

club student joindate leavedate
ABC Alice 2020-01-01 2020-01-06
ABC Bob 2020-01-01 null
ABC Charlie 2020-01-02 2020-01-12
XYZ Charlie 2020-01-02 null
ABC Alice 2020-01-12 null
XYZ Alice 2020-01-12 null

要在某个日期获得会员资格,您只需添加:

  AND joined.date <= '2020-01-06'
  AND coalesce(left_.date,'9999-12-31') > '2020-01-06'

  AND '2020-01-12' BETWEEN joined.date
                       AND coalesce(date_add(left_.date,interval -1 day),'9999-12-31')

由于 BETWEEN 包含在内,所以我减去一天。我已经使用 coalesce 来确保当没有对应的“左”时,比较的日期始终是最大日期。

但是,将以上具有相同“俱乐部”、“学生”和“加入”日期的记录分组等同于:

SELECT joined.club,
       joined.student,
       joined.date     AS joindate,
       min(left_.date) AS leavedate
  FROM Membership AS joined
  LEFT JOIN Membership AS left_ ON joined.club = left_.club
                               AND joined.student = left_.student
                               AND left_.status = 'Left'
                               AND left_.date >= joined.date
 WHERE joined.status = 'Joined'
 GROUP BY joined.club, joined.student, joined.date

添加日期将改为使用 HAVING,因为它在结果上:

HAVING '2020-01-12' BETWEEN joined 
   AND coalesce(date_add(`left`,interval -1 day),'9999-12-31')

我建议您使用 DATETIMETIMESTAMP 而不是 DATE,因为您将遇到这种情况:

Club    Student     Date          Status
B52     Peter       2020-10-03    Joined
B52     Peter       2020-10-03    Left
B52     Peter       2020-10-03    Joined

Club    Student     Date          Status
B52     Benny       2020-10-01    Joined
B52     Benny       2020-10-03    Left
B52     Benny       2020-10-03    Joined

“左派”大于或等于“加入派”中的任何一个,彼得和本尼在 3 日似乎都不是成员,因此在以后的任何日期也都不是成员。

如果您为您的 table 并检查“左”记录是否总是晚于“加入”记录,而不是检查 left_.date >= joined.date.

使“离开”日期严格大于“加入”日期似乎可以解决问题,但实际上没有人能够在与他们相同的日期离开加入了。

另请注意,使用 GROUP BY 的查询将消除 Peter 重复的“加入”日期。


dbfiddle