Sql 查找连续日期流中的每个第n个日期
我希望为给定日期范围内的每个用户在插入到表中的连续日期流中查找/标记每4天一次Sql 查找连续日期流中的每个第n个日期,sql,postgresql,Sql,Postgresql,我希望为给定日期范围内的每个用户在插入到表中的连续日期流中查找/标记每4天一次 CREATE TABLE mytable ( id INTEGER, myuser INTEGER, day DATE NOT NULL, PRIMARY KEY (id) ); 问题是,每个用户只有连续3天有效,之后必须有一天的“休息” id | myuser | day| -----+--------+------------+ 0 | 200 |
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天
2 | 200 | 2012-01-14 | }
3 | 200 | 2012-01-15 |连续3天
6 | 200 | 2012-01-18 | }
7 | 200 | 2012-01-19 |连续3天
10 | 201 | 2012-01-14 | }
11 | 201 | 2012-01-16 |我没有访问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
任何连续日期都将具有相同的用户组id。天中的每个“间隔”都会使该用户组id减少1(请参见第8行,如果记录是17日,间隔2天,则id将为1)
一旦您有了组id,就可以很容易地用row\u number()来表示它是序列中的哪一天。最多3天与“每4天应为间隔”相同,“x%4=0”表示每4天。更简单、更快,包括:
结果:
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
要点:
- 在连续三天之后,查询将所有天数标记为
过期
。现在还不清楚你是想在规则被打破后,还是仅仅每隔4天就对所有这些内容进行标记
- 我在开始日期(而不仅仅是两天)之前加入3天来确定第一天是否已经违反了规则
- 测试很简单:如果分区中当前行之前的第三行等于当前日期-3,则该规则已被打破。我用
COALESCE
将其全部包装起来,将NULL
值折叠为FALSE
,这只是为了美观。只要(myuser,day)
是唯一的,
在PostgreSQL中,可以从日期中减去整数,有效地减去天数
- 可以在单个查询级别中完成,无需CTE或子查询。应该快得多
- 您需要使用PostgreSQL 8.4或更高版本
什么样的SQL,有些工具在这里有很大帮助,有些没有。如果我扩展上一个示例,并说1月28日也在表中,1月31日应该是休息日。你需要那种“关注细节”的程度吗?或者说“2月1日是我真正关注的第一天,2月1日将显示为休息日”就足够了。(或者更简短地说,“我只会看兴趣范围前2天”?)应该忽略搜索日期范围之外的中断日期,因此是的,“我只会看兴趣范围前2天”。我正在努力理解,为什么这样做有效,但它有效。我想我必须深入研究一下我以前从未使用过的窗口函数。谢谢@return1.at-对不起,我没有解释工作原理,我不得不参加一个会议:)我现在添加了一些说明。我们应该提名他为总裁:)谢谢你指出lag()。但我认为我必须坚持Dems的答案,因为我需要每4天将其标记为break\u逾期
。我对这个问题进行了调整,使之更加精确。@Dems:Yep,自从它在v8.4中获得窗口函数以来。
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
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