【问题标题】:How to add a running count to rows in a 'streak' of consecutive days如何在连续几天的“连续”中向行添加运行计数
【发布时间】:2015-03-29 10:13:20
【问题描述】:

感谢Mike 提出添加创建/插入语句的建议。

create table test (
  pid integer not null,
  date date not null,
  primary key (pid, date)
);

insert into test values
  (1,'2014-10-1')
, (1,'2014-10-2')
, (1,'2014-10-3')
, (1,'2014-10-5')
, (1,'2014-10-7')
, (2,'2014-10-1')
, (2,'2014-10-2')
, (2,'2014-10-3')
, (2,'2014-10-5')
, (2,'2014-10-7');

我想添加一个新列,即“当前连续天数” 所以 结果 看起来像:

pid    | date      | in_streak
-------|-----------|----------
1      | 2014-10-1 | 1
1      | 2014-10-2 | 2
1      | 2014-10-3 | 3
1      | 2014-10-5 | 1
1      | 2014-10-7 | 1
2      | 2014-10-2 | 1
2      | 2014-10-3 | 2
2      | 2014-10-4 | 3
2      | 2014-10-6 | 1

我一直在尝试使用来自

的答案

但我不知道如何将 dense_rank() 技巧与其他窗口函数一起使用以获得正确的结果。

【问题讨论】:

  • 您说过,“pid 是唯一的,而 date 不是。”但是您的数据表明日期是唯一的,而 pid 不是。哪个是对的?
  • 日期不是唯一的,因为多个 pid 可以具有相同的日期。我会让问题更清楚。

标签: sql postgresql window-functions date-arithmetic gaps-and-islands


【解决方案1】:

如果您在问题中包含 CREATE TABLE 语句和 INSERT 语句,您会得到更多关注。

create table test (
  pid integer not null,
  date date not null,
  primary key (pid, date)
);

insert into test values
(1,'2014-10-1'), (1,'2014-10-2'), (1,'2014-10-3'), (1,'2014-10-5'),
(1,'2014-10-7'), (2,'2014-10-1'), (2,'2014-10-2'), (2,'2014-10-3'),
(2,'2014-10-5'), (2,'2014-10-7');

原理很简单。一连串不同的连续日期减去 row_number() 是一个常数。您可以按常数分组,然后将 dense_rank() 应用于该结果。

with grouped_dates as (
  select pid, date, 
         (date - (row_number() over (partition by pid order by date) || ' days')::interval)::date as grouping_date
  from test
)
select * , dense_rank() over (partition by grouping_date order by date) as in_streak
from grouped_dates
order by pid, date
pid 日期 grouping_date in_streak -- 1 2014-10-01 2014-09-30 1 1 2014-10-02 2014-09-30 2 1 2014-10-03 2014-09-30 3 1 2014-10-05 2014-10-01 1 1 2014-10-07 2014-10-02 1 2 2014-10-01 2014-09-30 1 2 2014-10-02 2014-09-30 2 2 2014-10-03 2014-09-30 3 2 2014-10-05 2014-10-01 1 2 2014-10-07 2014-10-02 1

【讨论】:

  • 当我运行它时,我没有在第一个 pid 组中得到中断,即连续不断。当我按照 Erwin 的建议在第二个选择中添加额外的 pid 分区时,它按预期工作。
【解决方案2】:

在此表上构建(不使用SQL keyword "date" 作为列名。):

CREATE TABLE tbl(
  pid int
, the_date date
, PRIMARY KEY (pid, the_date)
);

查询:

SELECT pid, the_date
     , row_number() OVER (PARTITION BY pid, grp ORDER BY the_date) AS in_streak
FROM  (
   SELECT *
        , the_date - '2000-01-01'::date
        - row_number() OVER (PARTITION BY pid ORDER BY the_date) AS grp
   FROM   tbl
) sub
ORDER  BY pid, the_date;

从另一个date 中减去date 得到integer。由于您正在寻找连续的天数,因此每下一行都会大 一个。如果我们从中减去row_number(),则整个连胜将在同一组中结束(grp)每个pid。然后很容易分配每组的数量。

grp 是用两次减法计算的,应该是最快的。一个同样快速的替代方案可能是:

the_date - row_number() OVER (PARTITION BY pid ORDER BY the_date) * interval '1d' AS grp

一乘一减。字符串连接和强制转换更昂贵。使用EXPLAIN ANALYZE 进行测试。

不要忘记在两个步骤中另外用pid进行分区,否则你会无意中混合了应该分开的组。

使用子查询,因为这通常比CTE 快。这里没有什么是普通子查询做不到的。

既然你提到了它:dense_rank() 显然 在这里没有必要。基本的row_number() 可以胜任。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多