【问题标题】:Find first occuring events in SQL在 SQL 中查找第一个发生的事件
【发布时间】:2020-09-22 08:22:06
【问题描述】:

问题

在我们的应用程序中,我们存储了房间何时应该被打扫的重复事件。任务(事件)被分配给用户并被赋予一个类别。我们使用 SQL 视图 cleaning_tasks 来获取特定日期的所有计划任务。该视图将返回如下内容。

SELECT * FROM cleaning_tasks WHERE scheduled_at = current_date();

> room_id, user_id, category, scheduled_at
>       1,       1,        3,   2020-06-04

现在的问题是,我们希望获得由room_iduser_idcategory 分组的第一个即将发生的事件。

例如,假设我们有三个必须每天打扫的类别 1 的房间和两个类别 2 的每个星期五打扫的房间。如果今天是星期三,那么我希望查询返回类别 1 的三个事件和计划在星期五发生的类别 2 的两个事件(标有 * 的行。)如果是星期五,那么查询将返回所有五场活动都安排在星期五。

room_id, user_id, category, scheduled_at
----------------------------------------
     10,       1,        1,   2020-06-03 * # Wednesday 
     20,       2,        1,   2020-06-03 *
     30,       3,        1,   2020-06-03 *
     10,       1,        1,   2020-06-04   # Thursday
     20,       2,        1,   2020-06-04
     30,       3,        1,   2020-06-04
     10,       1,        1,   2020-06-05   # Friday
     20,       2,        1,   2020-06-05
     30,       3,        1,   2020-06-05
     40,       4,        2,   2020-06-05 *         
     50,       5,        2,   2020-06-05 *

我的尝试

我尝试了以下查询并且得到了正确的结果,但由于GROUP BY,我不确定结果是否始终正确。 SQL 视图正在检索的事件是按顺序创建的。

SELECT room_id, user_id, category, scheduled_at
FROM room_cleaning_tasks
WHERE scheduled_at >= current_date()
GROUP BY room_id, user_id, category

我第一次尝试使用MIN,但发现我得到了错误的结果。可能是因为GROUP BY

SELECT room_id, user_id, category, scheduled_at, MIN(scheduled_at)
FROM cleaning_tasks
WHERE scheduled_at >= current_date()
GROUP_BY room_id, user_id, category

我也尝试在子查询中使用MIN,但没有奏效。我很确定由于子查询中的MIN 导致内部连接失败。

SELECT t.room_id, t.user_id, t.category, t.scheduled_at
FROM (
  SELECT room_id, user_id, category, MIN(scheduled_at) scheduled_at
  FROM cleaning_tasks
  GROUP BY room_id, user_id, category
) upcoming
INNER JOIN cleaning_tasks
  ON t.room_id = upcoming.room_id
  AND t.user_id = upcoming.user_id
  AND t.category = upcoming.category
  AND t.category >= current_date()

【问题讨论】:

    标签: sql date select mariadb greatest-n-per-group


    【解决方案1】:

    一个通常有效的选择是使用子查询进行过滤:

    select ct.* 
    from cleaning_tasks ct
    where ct.scheduled_at = (
        select min(ct1.scheduled_at)
        from cleaning_tasks ct1
        where ct1.room_id = ct.room_id and ct1.scheduled_at >= current_date
    )
    

    为了提高性能,请考虑在(room_id, scheduled_at) 上建立索引。

    【讨论】:

    • 遗憾的是,这个查询对我不起作用。我还添加了ct1.user_id = ct.user_id and ct1.category = ct.category。它只找到安排在 current_date 的事件。
    • @Gradox:here is a db fiddle。它似乎工作正常并产生了预期的结果(我过滤了room_id10,如您的问题中所述)。
    • 也许我的问题措辞不佳,但这些结果不是我所追求的。在示例中,房间 10 每个 user_id 每个 category 每个 scheduled_at 应该只出现一次。在你的小提琴室 10 出现了 3 次。在这种情况下,我应该只出现一次 schedule_at = 2020-06-03,因为它是关闭或等于current_date 的事件。再次,如果我不清楚,我很抱歉。
    • @Gradox:好吧,这样就更简单了!我修改了答案(并添加了性能建议)。
    • 我仍然需要比较 user_idcategory_id 以获得所需的结果,但我 99% 确信查询返回的正是我想要的。您的解决方案看起来比 @notmak 的简单,所以我会将您的解决方案标记为已接受的答案。不过,感谢你们俩的帮助!
    【解决方案2】:

    如果我的理解正确,您希望今天或之后的第一个“scheduled_at”日期按房间、用户和类别分组。

    我会使用 ROWNUMBER() OVER (PARTITION BY) 并将其放入子查询中,以便您进行过滤。

    例如:

    SELECT SUB.*
    FROM (
          SELECT ROOM_ID
          , USER_ID
          , CATEGORY
          , SCHEDULED_AT
          , ROW_NUMBER() OVER (PARTITION BY ROOM_ID, USER_ID, CATEGORY 
                               ORDER BY SCHEDULED_AT ASC) AS ITEM_NUMBER
          FROM CLEANING_TASKS
          WHERE SCHEDULED_AT >= CURRENT_DATE
          ) SUB
    WHERE ITEM_NUMBER = 1
    

    【讨论】:

    • 我认为这个查询给了我正确的行。我明天必须做一些测试,但看起来很有希望!
    • 虽然这两个查询都有效,但考虑到我们在生产中的数据集,这个解决方案的速度要快得多。
    猜你喜欢
    • 1970-01-01
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 2019-09-04
    相关资源
    最近更新 更多