【问题标题】:Query to find active records on a given date in a table recording "start" and "stop" dates查询以在记录“开始”和“停止”日期的表中查找给定日期的活动记录
【发布时间】:2020-01-09 11:05:06
【问题描述】:

我有一个记录在表格中的“开始/停止”活动列表,每个活动都与一个日期相关联。我需要确定哪些用户在特定日期“开始” - 即正在进行任务。我当前的设置和查询可以用这个简单的视图来表示:

CREATE TABLE `registration_statuses` (
    `status_id` INT(11) NOT NULL AUTO_INCREMENT,
    `status_user_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `status_activity` ENUM('start','stop') DEFAULT 'start',
    `status_date` DATE NULL DEFAULT NULL,
    PRIMARY KEY (`status_id`),
    INDEX `status_user_id` (`status_user_id`)
);

INSERT INTO `registration_statuses` (`status_user_id`, `status_activity`, `status_date`)
VALUES (1, 'start', '2020-01-01'),
       (2, 'start', '2020-01-02'),
       (1, 'stop', '2020-01-19'),
       (1, 'start', '2020-01-25'),
       (2, 'stop', '2020-01-31'),
       (1, 'stop', '2020-01-31');

然后我运行这个查询:

SELECT `rs`.`status_user_id`
FROM `registration_statuses` `rs`
  INNER JOIN (
    SELECT `status_user_id`, MAX(status_date) `last_date`
    FROM `registration_statuses`
    WHERE `status_date` < '2020-01-03'
    GROUP BY `status_user_id`
  ) `srs` ON `rs`.`status_user_id` = `srs`.`status_user_id`
            AND `rs`.`status_date` = `srs`.`last_date`
WHERE `status_activity` = 'start';

(见http://sqlfiddle.com/#!9/c8d371/1/0

通过更改查询中的日期,此查询会返回一个用户 ID 列表,告诉我谁在该特定日期参与(即已开始一项任务)。但是,用户被认为(在现实生活中)在他们停止任务的实际日期参与了任务。此查询不允许这样做,因为如果您要更改查询中的日期以反映 2020-01-19,即用户 1 停止的那一天,则查询将仅返回用户 2。

我尝试将&lt;= 条件更改为严格的&lt;,虽然这解决了部分问题,但用户在开始的那天并不被认为是忙碌的。使用严格的&lt;,仅在“2019-01-25”返回用户,而我希望两个用户都出现。

在这一点上,我唯一“可行”的解决方案是合并两个版本查询的结果(以DISTINCT / UNION 查询的形式),但我不禁想到必须有一种更有效的方法来获得我需要的结果。

【问题讨论】:

  • 到目前为止做得很好,但是在提供示例数据集时,包含将从所需结果中排除的数据通常会很有帮助。而且,澄清一下,我认为您希望用户在 2020 年 1 月 19 日之前参与了一项任务?
  • @Strawberry 嗨-我想我有...例如,如果您在 1 月 20 日查看,则应排除用户 1。我误会你了吗?

标签: mysql sql temporal-database


【解决方案1】:

这有帮助吗?

SELECT a.status_id
     , a.status_user_id 
     , a.status_date start
     , MIN(b.status_date) stop
  FROM registration_statuses a
  LEFT
  JOIN registration_statuses b
    ON b.status_user_id = a.status_user_id
   AND b.status_id > a.status_id
   AND b.status_activity = 'stop'
 WHERE a.status_activity = 'start'
 GROUP 
    BY a.status_id;

+-----------+----------------+------------+------------+
| status_id | status_user_id | start      | stop       |
+-----------+----------------+------------+------------+
|         1 |              1 | 2020-01-01 | 2020-01-19 |
|         2 |              2 | 2020-01-02 | 2020-01-31 |
|         4 |              1 | 2020-01-25 | 2020-01-31 |
+-----------+----------------+------------+------------+

【讨论】:

  • 是的,我认为这会有所帮助。从这里我可以使用 where 条件来检查开始和停止日期,允许它包含停止日期。剩下要回答的唯一问题是它的性能,但这很容易测试。
  • 只是另一种表示感谢:这提供了一个良好的开端。我修改了您的查询以包括活动状态(因为实际上我们不仅仅是“开始”和“停止”)。这为进一步过滤提供了良好的基础。查询似乎也能很好地处理数十万条记录。
【解决方案2】:

一种方法是相关子查询:

select rs.*
from registration_statuses rs
where rs.status_date = (select max(rs2.status_date)
                        from registration_statuses rs2
                        where rs2.status_user_id = rs.status_user_id and
                              rs2.status_date <= ?
                       ) and
      rs.status_activity = 'active';

为了提高性能,您需要在registration_statuses(status_user_id, status_date) 上建立索引。

还有其他有趣的方法。如果你只想要user_id,这里是一种只使用聚合的方法:

select rs.user_id
from registration_statuses rs
where rs.status_date <= ?
group by rs.user_id
having max(rs.status_date) = max(case when rs.status_activity = 'active' then status_date end);

也就是说,选择截至特定日期最近状态日期为“活跃”的用户。

【讨论】:

  • 感谢您的尝试,但这些都没有帮助。第一个查询不会在“2019-01-19”上返回用户 1,因此不适合。您的第二个查询中的字段名称已损坏,具体而言,status_end 是什么意思?
  • @Philip 。 . .该用户于 2019-01-19 停止,它似乎不活跃。但是,您可以通过在第一个查询中将 &lt;= 更改为 &lt; 来轻松包含它。使用指定的索引,它应该比聚合查询更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-20
相关资源
最近更新 更多