【问题标题】:find every n-th date in a continuous date stream在连续日期流中查找每个第 n 个日期
【发布时间】:2012-01-12 14:30:01
【问题描述】:

我想在给定日期范围内为每个用户插入到我的表中的连续日期流中每隔 4 天查找/标记一次

CREATE TABLE mytable (
  id INTEGER,
  myuser INTEGER,            
  day DATE NOT NULL,
  PRIMARY KEY  (id)      
);

问题是,每个用户只有连续 3 天有效,之后,必须有一天的“休息”

 id  | myuser |    day     |
-----+--------+------------+
  0  |    200 | 2012-01-12 | }
  1  |    200 | 2012-01-13 | }--> 3 continuous days
  2  |    200 | 2012-01-14 | }
  3  |    200 | 2012-01-15 | <-- not ok, user 200 should get warned and delete this
  4  |    200 | 2012-01-16 | }
  5  |    200 | 2012-01-17 | }--> 3 continuous days
  6  |    200 | 2012-01-18 | }
  7  |    200 | 2012-01-19 | <-- not ok, user 200 should get warned and delete this
  8  |    201 | 2012-01-12 | }
  9  |    201 | 2012-01-13 | }--> 3 continuous days
  10 |    201 | 2012-01-14 | }
  11 |    201 | 2012-01-16 | <-- ok, there is a one day gap here
  12 |    201 | 2012-01-17 | 

主要目标是查看给定的日期范围(通常是一个月)并确定不允许的日期。此外,我必须注意正确处理重叠日期,例如,如果我查看从 2012-02-01 到 2012-02-29 的日期范围,2012-02-01 可能是“休息”日,如果2012-01-29 到 2012-01-31 存在于同一用户的该表中。

【问题讨论】:

  • 什么种类的 SQL,有些工具可以在这里提供显着帮助,有些则没有。
  • 如果我扩展上一个示例,并说 1 月 28 日也在表格中,那么 1 月 31 日应该是休息日。您是否需要那种“对细节的关注”,或者说“2 月 1 日是我真正关注的第一天,2 月 1 日将显示为休息日”就足够了。 (或者更短一点,“我只会在感兴趣的范围前 2 天查看”?)
  • 超出搜索日期范围的休息日期应该被忽略,所以是的 “我只会在感兴趣的范围前 2 天查看。”

标签: sql postgresql


【解决方案1】:

我无权访问 PostgreSQL,但希望这可行...

WITH
  grouped_data AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY myuser ORDER BY day) - (day - start_date) AS user_group_id,
    myuser,
    day
  FROM
    myTable
  WHERE
        day >= start_date - 3
    AND day <= end_date
)
,
  sequenced_data AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY myuser, user_group_id ORDER BY day) AS sequence_id,
    myuser,
    day
  FROM
    grouped_data
)
SELECT
  myuser,
  day,
  CASE WHEN sequence_id % 4 = 0 THEN 1 ELSE 0 END as should_be_a_break_day
FROM
  sequenced_data
WHERE
  day >= start_date


抱歉,我没有解释工作原理,我不得不参加会议:)

start_date = '2012-01-14' 的示例...

 id | myuser |    day     | ROW_NUMBER() | day - start_date | user_group_id
----+--------+------------+--------------+------------------+---------------
  0 |    200 | 2012-01-12 |     1        |        -2        |  1 - -2 = 3
  1 |    200 | 2012-01-13 |     2        |        -1        |  2 - -1 = 3
  2 |    200 | 2012-01-14 |     3        |         0        |  3 -  0 = 3
  3 |    200 | 2012-01-15 |     4        |         1        |  4 -  1 = 3
  4 |    200 | 2012-01-16 |     5        |         2        |  5 -  2 = 3
----+--------+------------+--------------+------------------+---------------
  5 |    201 | 2012-01-12 |     1        |        -2        |  1 - -2 = 3
  6 |    201 | 2012-01-13 |     2        |        -1        |  2 - -1 = 3
  7 |    201 | 2012-01-14 |     3        |         0        |  3 - -1 = 3
  8 |    201 | 2012-01-16 |     4        |         2        |  4 -  2 = 2

任何连续日期都将具有相同的 user_group_id。天数中的每个“间隔”都会使 user_group_id 减少 1(参见第 8 行,如果记录是 17 日,间隔 2 天,id 将是 1)

一旦你有了 group_id,row_number() 就可以很容易地用来说明它是序列中的哪一天。最多 3 天与“每 4 天应间隔一次”相同,“x % 4 = 0”表示每 4 天。

【讨论】:

  • atm 我正在努力理解,为什么这有效,但它有效。猜想我必须深入研究一下我以前从未使用过的窗口函数。谢谢!
  • @return1.at - 抱歉,我没有解释工作原理,我不得不参加会议 :) 我现在添加了一些描述。
  • 我们应该提名 OVER 为总裁 :))
【解决方案2】:

使用window function lag() 更简单、更快捷

SELECT myuser
      ,day
      ,COALESCE(lag(day, 3) OVER (PARTITION BY myuser ORDER BY day) = (day - 3)
                ,FALSE) AS break_overdue
FROM   mytable
WHERE  day BETWEEN ('2012-01-12'::date - 3) AND '2012-01-16'::date;

结果:

 myuser |    day     | break_overdue
--------+------------+---------------
    200 | 2012-01-12 | f
    200 | 2012-01-13 | f
    200 | 2012-01-14 | f
    200 | 2012-01-15 | t
    200 | 2012-01-16 | t
    201 | 2012-01-12 | f
    201 | 2012-01-13 | f
    201 | 2012-01-14 | f
    201 | 2012-01-16 | f

要点:

  • 查询连续三天后将all days标记为break_overdue。目前尚不清楚您是希望在违反规则后标记所有标记,还是每 4 天标记一次。

  • 我在开始日期前包含 3 天(不仅仅是两天),以确定第一天是否已经违反规则。

  • 测试很简单:如果分区内当前行之前的第 3 行等于当天 - 3,则规则已被破坏。我将其全部包装在 COALESCE 中以将 NULL 值折叠为 FALSE 仅出于美观原因。只要(myuser, day)唯一,保证工作。
    在 PostgreSQL 中,您可以从日期中减去整数,有效地减去天数。

  • 可以在单个查询级别中完成,无需 CTE 或子查询。应该更快。

  • window functions 需要 PostgreSQL 8.4 或更高版本。

【讨论】:

  • 感谢您指出lag()。但我认为我必须坚持 Dems 的回答,因为我需要每隔 4 天将其标记为break_overdue。我调整了这个问题,使其更准确。
  • @Dems:是的,自从它在 v8.4 中获得了窗口函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-23
  • 1970-01-01
  • 2016-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多