Как сгруппировать последовательные метки времени в таблице SQL?

У меня есть таблица SQL в SQL Server с временными метками, которая выглядит так:

  │ Timestamps
──┼────────────────────
1 │ 2022-09-23 15:01:00
2 │ 2022-09-23 15:02:00
3 │ 2022-10-03 14:52:00
4 │ 2022-10-03 14:53:00
5 │ 2022-10-03 14:54:00
6 │ 2022-10-03 14:56:00
7 │ 2022-10-03 14:57:00
8 │ 2022-10-03 14:58:00
9 │ 2022-10-03 14:59:00

Я хочу извлечь из таблицы все последовательные диапазоны дат, где разница между каждой отметкой времени в диапазоне составляет всего одну минуту. Это желаемый результат:

  │ From                │ To
──┼─────────────────────┼─────────────────────
1 │ 2022-09-23 15:01:00 │ 2022-09-23 15:02:00
2 │ 2022-10-03 14:52:00 │ 2022-10-03 14:54:00
3 │ 2022-10-03 14:56:00 │ 2022-10-03 14:59:00

Обратите внимание, что, например. строки 3, 4 и 5 сгруппированы в одну строку, потому что метки времени 2022-10-03 14:52, 2022-10-03 14:53 и 2022-10-03 14:54 идут подряд. Остальные временные метки от 2022-10-03 14:56 до 2022-10-03 14:59 группируются в свой собственный диапазон, потому что между диапазонами есть разрыв (где был бы 2022-10-03 14:55).

Что такое SQL-запрос, который достигает этого?

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
0
67
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это проблема gaps and islands,

Использование первого cte для поиска различий между последовательными строками с помощью оконной функции LEAD() и второго cte для поиска идентификатора группы для каждой последовательной строки:

with cte as (
    select *, lead(Timestamps) over (order by Timestamps) as lead,
              case when 
              datediff(mi, Timestamps, LEAD(Timestamps) over (order by Timestamps)) = 1 then 0 else 1 end as diff
    from mytable
),
cte2 as (
  select *, sum(diff) over(order by Timestamps) as grp
  from cte
)
select grp+1 as range_id, min(Timestamps) as [From], max(lead) as [To]
from cte2
where lead is not null
group by grp

Результат :

range_id    From                    To
1           2022-09-23 15:01:00.000 2022-09-23 15:02:00.000
2           2022-09-23 15:02:00.000 2022-10-03 14:54:00.000
3           2022-10-03 14:54:00.000 2022-10-03 14:59:00.000

Демо здесь

Ответ принят как подходящий

Кажется, это классическая игра «Гэпс-энд-Айлендс».

Пример

Select TS1 = min([timestamps])
      ,TS2 = max([timestamps])
 From ( 
        Select *
              ,Grp = row_number() over( order by [timestamps])
                   - datediff(minute,0,[timestamps])
         From YourTable
      ) A
 Group By Grp

Полученные результаты

TS1                         TS2
2022-10-03 14:56:00.000     2022-10-03 14:59:00.000
2022-10-03 14:52:00.000     2022-10-03 14:54:00.000
2022-09-23 15:01:00.000     2022-09-23 15:02:00.000

Большое спасибо! Красивое и аккуратное решение! 🚀 🥂

Daniel Jonsson 17.05.2023 10:52

Вы также можете использовать сеансизацию для этого, я считаю, что это более читабельно: создайте новый идентификатор сеанса, как только разрыв между двумя временными метками не составит 1 минуту. Двухэтапный запрос со счетчиком, установленным на 1 каждый раз, когда не проходит одна минута, и запрос вокруг него, который получает текущую сумму этого счетчика:

WITH
-- your input ...
indata(id,ts) AS (
          SELECT 1,{ts '2022-09-23 15:01:00'}
UNION ALL SELECT 2,{ts '2022-09-23 15:02:00'}
UNION ALL SELECT 3,{ts '2022-10-03 14:52:00'}
UNION ALL SELECT 4,{ts '2022-10-03 14:53:00'}
UNION ALL SELECT 5,{ts '2022-10-03 14:54:00'}
UNION ALL SELECT 6,{ts '2022-10-03 14:56:00'}
UNION ALL SELECT 7,{ts '2022-10-03 14:57:00'}
UNION ALL SELECT 8,{ts '2022-10-03 14:58:00'}
UNION ALL SELECT 9,{ts '2022-10-03 14:59:00'}
)
-- real query starts here - replace following comma with "WITH" 
,
-- sessionization part 1: counter at 1 if gap > 1 min
sess1 AS (
  SELECT
   *   
  ,CASE
     WHEN DATEDIFF(minute,LAG(ts) OVER(ORDER BY ts), ts) = 1 THEN 0
     ELSE 1
   END AS counter
  FROM indata
)
,
-- get the running sum of the obtained counter above to get a session id
sess2 AS (
  SELECT
    id
  , ts
  , SUM(counter) OVER(ORDER BY ts) AS session_id
  FROM sess1
)
SELECT 
  session_id
, MIN(ts) AS from_ts
, MAX(ts) AS to_ts
FROM sess2
GROUP BY session_id
ORDER BY 1

идентификатор сессии from_ts to_ts 1 2022-09-23 15:01:00.000 2022-09-23 15:02:00.000 2 2022-10-03 14:52:00.000 2022-10-03 14:54:00.000 3 2022-10-03 14:56:00.000 2022-10-03 14:59:00.000

рабочий пример

Это в значительной степени идентично другому ответу здесь.

Stu 14.05.2023 14:24

Другие вопросы по теме