У меня есть ситуация, когда я пытаюсь рассчитать время начала, окончания и время, проведенное в разных областях в одном месте.
У меня есть данные с отметкой времени и местоположением человека, когда он был захвачен системой.
Обычным случаем является изменение местоположения, и в этом случае время окончания должно быть предыдущим увиденным значением. Исключением является случай, когда человек не виден в течение 5 м или более, и в этом случае endTime должен быть последним увиденным (см. выше строки 2 и 3 требуемых агрегированных выходных данных).
Исходные данные
date, zone
8h10m, room1
8h12m, room1
8h15m, hall
8h16m, hall
8h25m, hall
8h29m, hall
8h30m, room2
8h34m, room2
8h38m, room2
8h42m, room2
Агрегация/Сводка требуется следующим образом (или подобным):
startDate, endDate, time, zone
8h10m, 8h12m, 3m, room1
8h15m, 8h16m, 2m, hall <-- special case time >5m
8h25m, 8h29m, 5m, hall
8h30, 8h42m, 9n, room2
Можете ли вы дать мне представление о том, как я могу сделать такое «агрегирование/сводку» в SQL? Я использую BigQuery, но я считаю, что стандартный SQL должен работать.
Спасибо,
Руи
В последней строке указано: 8h30, 8h42m, 9n, room2 ---> 13m ?
да Сэмюэл, 13м... моя ошибка ;-)
Из решения Михаила Берлянта концепция с countif
была использована для упрощения этого запроса. Этот ответ способен идентифицировать каждое движение, даже если человек повторно входит в комнату в течение 5 минут. Дополнительные данные см. в таблице.
Необходимо несколько шагов:
---
, когда 5 минут нет данных: Установите over_5:minutes
true, если разница между предыдущей строкой (lag
) и текущей строкой превышает 5 минут. unnest([0,1]) as x
для дублирования набора данных и qualify
для включения набора данных в этом случае.date, x
в over(order by date, x)
lag
получить последнюю комнату и последнюю дату. Из-за невложенного x посмотрите на две строки назад.zone_change
на true.countif (zone_change)
с 1 по текущую дату, чтобы получить zone_id
. Это соответствует одной зоне.zone_id
посчитайте время, когда x
было равно 0; это тот случай, когда 5 минут ни одной позиции не дали.group by zone_id
и рассчитать минимальную и максимальную дату---
путем фильтрацииWith tbl as
(
SELECT TIME "8:10:00" as date, "room1" as zone
UNION ALL SELECT TIME "8:12:00", "room1"
UNION ALL SELECT TIME "8:15:00", "hall"
UNION ALL SELECT TIME "8:16:00", "hall"
UNION ALL SELECT TIME "8:25:00", "hall"
UNION ALL SELECT TIME "8:29:00", "hall"
UNION ALL SELECT TIME "8:30:00", "room2"
UNION ALL SELECT TIME "8:34:00", "room2"
UNION ALL SELECT TIME "8:38:00", "room2"
UNION ALL SELECT TIME "8:42:00", "room2"
UNION ALL SELECT TIME "8:43:00", "hall"
UNION ALL SELECT TIME "8:44:00", "room2"
)
SELECT
zone_id,
zone,
min(date) as startDate,
max(date) as endDate,
time_diff(max(date),min(date),minute)+1 as time_minutes
FROM
(
SELECT *,
countif (x=0) over (ORDER BY date,x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)+
countif (zone_change) over (ORDER BY date,x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as zone_id
FROM
(
SELECT date,x,if (x=1,zone,"---") as zone,
time_diff(date,lag(date,2) over (order by date),minute)>5 as over_5_minutes,
zone!=lag(zone,2) over (order by date,x) as zone_change
FROM tbl, unnest([0,1]) as x
Qualify over_5_minutes or x=1
)
)
where zone! = "---"
group by 1,2
order by 1
Спасибо @Samuel! Я тоже попробую ваше решение. Хорошо, когда есть решения на выбор. Ценю твою поддержку. Руи
Рассмотрим ниже подход
select
min(date) as startDate, max(date) as endDate,
time_diff(max(date), min(date), minute) + 1 as time, zone
from (
select *, countif (new_zone) over (partition by zone order by date) as zone_number
from (
select *,
ifnull(date - lag(date) over (partition by zone order by date) > make_interval(minute => 5)
or zone != lag(zone) over(order by date), true) as new_zone
from your_table
)
)
group by zone, zone_number
если применяется к выборочным данным в вашем вопросе
with your_table as (
select time "8:10:00" as date, "room1" as zone union all
select "8:12:00", "room1" union all
select "8:15:00", "hall" union all
select "8:16:00", "hall" union all
select "8:25:00", "hall" union all
select "8:29:00", "hall" union all
select "8:30:00", "room2" union all
select "8:34:00", "room2" union all
select "8:38:00", "room2" union all
select "8:42:00", "room2"
)
выход
Очень хороший и короткий запрос для этой задачи. Если человек снова войдет в ту же зону в течение 5 минут, он не будет показан. Когда человек идет в 9:43 в холл, а затем возвращается в комнату 2 в 9:44, будет показан 2-й зал, но не 2-я комната2.
спасибо! @Samuel - ты прав - исправлено :о)
Большое спасибо! работает, как и ожидалось... просто пришлось изменить time_diff на date_diff, так как я использую поле типа TIMESTAMP в BigQuery. все еще пытаюсь понять логику, но я доберусь ;-) Еще раз спасибо, Руи
какой тип данных поля
date
? это действительно строка, как в примере данных в вашем вопросе? или это дата и время или временная метка, как и должно быть в идеале?