PostgreSQL - GROUP后续行

时间:2013-12-07 20:29:57

标签: postgresql group-by window-functions

我有一张表,其中包含按日期排序的一些记录。

我希望获得每个后续组的开始和结束日期(按某些标准分组,例如,位置)。

Example

create table tbl (id int, date timestamp without time zone, 
                  position int);

insert into tbl values 
( 1 , '2013-12-01', 1),
( 2 , '2013-12-02', 2),
( 3 , '2013-12-03', 2),
( 4 , '2013-12-04', 2),
( 5 , '2013-12-05', 3),
( 6 , '2013-12-06', 3),
( 7 , '2013-12-07', 2),
( 8 , '2013-12-08', 2)

当然,如果我只是按位置分组,我会得到错误的结果,因为不同的小组的位置可能相同:

SELECT POSITION, min(date) MIN, max(date) MAX
FROM tbl GROUP BY POSITION

我会得到:

POSITION    MIN                             MAX
1           December, 01 2013 00:00:00+0000 December, 01 2013 00:00:00+0000
3           December, 05 2013 00:00:00+0000 December, 06 2013 00:00:00+0000
2           December, 02 2013 00:00:00+0000 December, 08 2013 00:00:00+0000

但我想:

POSITION    MIN                             MAX
1           December, 01 2013 00:00:00+0000 December, 01 2013 00:00:00+0000
2           December, 02 2013 00:00:00+0000 December, 04 2013 00:00:00+0000
3           December, 05 2013 00:00:00+0000 December, 06 2013 00:00:00+0000
2           December, 07 2013 00:00:00+0000 December, 08 2013 00:00:00+0000

我找到了一个使用变量的solution for MySql我可以移植它但是我相信PostgreSQL可以使用它的高级功能(如窗口函数)以更智能的方式完成它。

我正在使用PostgreSQL 9.2

2 个答案:

答案 0 :(得分:1)

Stackoverflow上有一些完整的答案,所以我不会详细重复它们,但它的原理是根据不同之处对记录进行分组:

  • 按日期排序时的行号(通过窗口功能)
  • 日期与静态参考日期之间的差异。

所以你有一系列如:

rownum datediff diff
1      1        0 ^
2      2        0 | first group
3      3        0 v
4      5        1 ^
5      6        1 | second group
6      7        1 v
7      9        2 ^
8      10       2 v third group

答案 1 :(得分:1)

可能有更优雅的解决方案,但试试这个:

WITH tmp_tbl AS (
SELECT *,
CASE WHEN lag(position,1) OVER(ORDER BY id)=position 
    THEN position 
    ELSE ROW_NUMBER() OVER(ORDER BY id)
    END AS grouping_col  
FROM tbl
)
, tmp_tbl2 AS(
SELECT position,date,
CASE WHEN lag(position,1)OVER(ORDER BY id)=position 
    THEN lag(grouping_col,1) OVER(ORDER BY id)
    ELSE ROW_NUMBER() OVER(ORDER BY id) 
    END AS grouping_col
FROM tmp_tbl
)
SELECT POSITION, min(date) MIN, max(date) MAX
FROM tmp_tbl2 GROUP BY grouping_col,position