如何在“条纹”中的行中添加运行计数。连续几天

时间:2015-01-30 00:40:19

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

感谢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()技巧与其他窗口函数一起使用来获得正确的结果。

2 个答案:

答案 0 :(得分:10)

在此表的基础上构建(不使用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()完成了这项工作。

答案 1 :(得分:3)

如果在问题中包含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  date         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