У меня есть таблица 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-запрос, который достигает этого?


Это проблема 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
Вы также можете использовать сеансизацию для этого, я считаю, что это более читабельно: создайте новый идентификатор сеанса, как только разрыв между двумя временными метками не составит 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
Это в значительной степени идентично другому ответу здесь.
Большое спасибо! Красивое и аккуратное решение! 🚀 🥂