如何使用 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 = 0
到 raw_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 获取最新记录?