Как использовать РАЗДЕЛЕНИЕ SQL ПО ГРУППАМ?

#sql #postgresql #window-functions

#sql #postgresql #окно-функции

Вопрос:

Я работаю с 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;
  

У Google не так много информации об этом, но, согласно документам, предложение «GROUPS» — это именно то, что мне нужно, но конечный результат неверен, потому что оконные функции просто копируют значение из столбца timestamp:

 | 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) без оконных функций.

Комментарии:

1. PARTITION BY raw_value будет связывать все строки с одинаковыми raw_value , независимо от того, есть ли строки с разными raw_value между ними или нет. Это проблема «пробелов и островов»; найдите это ключевое слово.

Ответ №1:

Определите группы с помощью row_number() - sum() трюка, затем выберите минимальное и максимальное время для каждой идентифицированной группы.

 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;
  

Здесь скрипка с БД.

( GROUPS Предложение зависит от порядка окон и, похоже, не имеет ничего общего с вашей проблемой.)

Ответ №2:

Ваша обновленная скрипка здесь.

Для подхода с пробелами и островами сначала отметьте переходы с 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
)
  

Используйте group by эти grp_num значения, чтобы получить желаемый результат.

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