如何使用 SQL PARTITION BY GROUPS?

Posted

技术标签:

【中文标题】如何使用 SQL PARTITION BY GROUPS?【英文标题】:How to use SQL PARTITION BY GROUPS? 【发布时间】:2020-08-17 15:17:15 【问题描述】:

我正在使用 PostgreSQL 12,但问题是标准 SQL。 我有一张这样的桌子:

| timestamp                | raw_value |
| ------------------------ | --------- |
| 2015-06-27T03:52:50.000Z | 0         |
| 2015-06-27T03:53:00.000Z | 0         |
| 2015-06-27T03:53:10.000Z | 1         |
| 2015-06-27T03:53:20.000Z | 1         |
| 2015-06-27T04:22:20.000Z | 1         |
| 2015-06-27T04:22:30.000Z | 0         |
| 2015-06-27T05:33:40.000Z | 1         |
| 2015-06-27T05:33:50.000Z | 1         |

我需要获取每个组的第一个和最后一个时间戳,raw_value = 1,即需要的结果

| start_time               | end_time                 |
| ------------------------ | ------------------------ |
| 2015-06-27T03:53:10.000Z | 2015-06-27T04:22:20.000Z |
| 2015-06-27T05:33:40.000Z | 2015-06-27T05:33:50.000Z |

到目前为止,我的最大努力是这样的:

SELECT timestamp, raw_value, row_number() over w as rn, first_value(obt) OVER w AS start_time, last_value(obt) OVER w AS end_time
FROM mytable
WINDOW w AS (PARTITION BY raw_value ORDER BY timestamp GROUPS CURRENT ROW )
ORDER BY timestamp;

谷歌没有太多关于它的信息,但根据docs,“GROUPS”子句正是我需要的,但最终结果是错误的,因为窗口函数只是从时间戳列中复制值:

| timestamp                | raw_value | rn  | start_time               | end_time                 |
| ------------------------ | --------- | --- | ------------------------ | ------------------------ |
| 2015-06-27T03:52:50.000Z | 0         | 1   | 2015-06-27T03:52:50.000Z | 2015-06-27T03:52:50.000Z |
| 2015-06-27T03:53:00.000Z | 0         | 2   | 2015-06-27T03:53:00.000Z | 2015-06-27T03:53:00.000Z |
| 2015-06-27T03:53:10.000Z | 1         | 1   | 2015-06-27T03:53:10.000Z | 2015-06-27T03:53:10.000Z |
| 2015-06-27T03:53:20.000Z | 1         | 2   | 2015-06-27T03:53:20.000Z | 2015-06-27T03:53:20.000Z |
| 2015-06-27T04:22:20.000Z | 1         | 3   | 2015-06-27T04:22:20.000Z | 2015-06-27T04:22:20.000Z |
| 2015-06-27T04:22:30.000Z | 0         | 3   | 2015-06-27T04:22:30.000Z | 2015-06-27T04:22:30.000Z |
| 2015-06-27T05:33:40.000Z | 1         | 4   | 2015-06-27T05:33:40.000Z | 2015-06-27T05:33:40.000Z |
| 2015-06-27T05:33:50.000Z | 1         | 5   | 2015-06-27T05:33:50.000Z | 2015-06-27T05:33:50.000Z |

在第 6 行,我希望行号重置为 1,但事实并非如此!我也尝试过使用BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,但没有运气。

为了您的方便,我还创建了一个DB Fiddle 链接。

如果有任何其他方法可以在没有窗口函数的情况下在 SQL(可以是 PG 特定)中实现相同的结果,我想知道。

【问题讨论】:

PARTITION BY raw_value 会将 所有 行与相同的 raw_value 捆绑在一起,无论它们之间是否存在具有不同 raw_value 的行。这是一个“差距和孤岛”问题;搜索该关键字。 【参考方案1】:

使用row_number() - sum()trick 识别组,然后为每个识别的组选择最短和最长时间。

with grp as (
  select obt, raw_value
       , row_number() over w - sum(raw_value) over w as g
  from tm_series
  window w as (order by obt)
)
select min(obt), max(obt)
from grp
where raw_value = 1
group by g;

DB 小提琴here.

GROUPS 子句取决于窗口顺序,似乎与您的问题没有共同点。)

【讨论】:

【参考方案2】:

你的updated fiddle here。

对于间隙和孤岛方法,首先标记您从 raw_value = 0raw_value = 1 的转换

with mark_changes as (
  select obt, raw_value,
         case
           when raw_value = 0 then 0
           when raw_value = lag(raw_value) over (order by obt) then 0
           else 1
         end as transition
    from tm_series
), 

只保留raw_value = 1 行和sum() 前面的transition 标记以将每一行放入一个组中。

id_groups as (
  select obt, raw_value, 
         sum(transition) over (order by obt) as grp_num
    from mark_changes
   where raw_value = 1
)

在这些 grp_num 值上使用 group by 以获得您想要的结果。

select min(obt) as start_time, 
       max(obt) as end_time
  from id_groups
 group by grp_num
 order by min(obt);

【讨论】:

以上是关于如何使用 SQL PARTITION BY GROUPS?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用“Partition By”或“Max”?对于 SQL 服务器

如何在不使用 GROUP BY 或 PARTITION BY 的情况下对 Oracle SQL 中的数据进行分组

如何在 SQL 中以高性能的方式使用 PARTITION BY 获取最新记录?

如何在linq to sql中使用orderby和partition by获取第一行

【SQL】partition by

SQL中如何使用over-partition by query获取当前值、平均值和最大值?