2015-06-16 2 views
2

Я новичок в BQ и не знаю, сколько это будет стоить, выполнив этот запрос.Bigquery - рассчитать активные сессии пользователей из истории доступа

У меня есть таблица, которая записывает время доступа всех пользователей, как следующее:

user_id  access_time 
------------------------------------- 
user_a  2015-06-15 14:12:12 
user_b  2015-06-15 14:12:12 
user_a  2015-06-15 14:12:13 
user_a  2015-06-15 14:12:19 
user_a  2015-06-15 14:12:28 
user_a  2015-06-15 19:32:15 
user_a  2015-06-15 19:32:19 

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

Сессия истекает, если следующий доступ не находится в пределах 10 секунд.

Пример таблицы сессии будет:

session_id user_id session_start_time duration 
------------------------------------------------------------ 
1    user_a  2015-06-15 14:12:12 16 
2    user_b  2015-06-15 14:12:12 0 
3    user_a  2015-06-15 19:32:15 4 

Кажется, что BQ не поддерживает настраиваемую функцию, как можно достичь этого одного запроса?

Заранее благодарен!


ОБНОВЛЕНИЕ:

Фиксированный пример.

ответ

1

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

  1. Для каждого события, используйте функцию LEAD(), чтобы найти следующий время доступа; вычислить разницу и запустить оператор if из результата, чтобы отметить запись как «новый сеанс» 1/0. Возьмите только новые сеансы. Это даст вам вложенной таблицу все начала сеанса периодов
  2. Следуйте те же шаги, за исключением слабеющих новых сеансов, чтобы получить продолжительность каждого доступа
  3. Объединить два суб-таблицы на что-то как:

    на a.user_id = b.user_id и b.access_time> = a.session_start_time и b.access_time < next_session_time

  4. Тогда просто просуммировать для каждого пользователя и сессии

Возможно, не самый эффективный подход (сохранить частичные результаты в таблице темп, чтобы избежать пробега по всем данным дважды), но он должен работать

3

Чтобы проиллюстрировать подход, используя данные из вашего примера, вот как запрос бы который показывает новые сеансы с временем начала выглядеть следующим образом:

select user, ts start_time from (
select user, ifnull(seconds - prev_seconds > 10, true) new_session from (
select user, ts, seconds, lag(seconds, 1) over(partition by user order by seconds) prev_seconds from 
(select user, ts, integer(ts/1000000) seconds from 
(select 'user_a' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_b' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:13') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:19') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:28') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:15') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:19') ts)))) 
where new_session 

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

select user, ts, if(next_is_last, next_seconds - seconds, 0) duration 
from (
select 
    user, new_session, last_session, ts, seconds, 
    lead(seconds, 1) over(partition by user order by seconds) next_seconds, 
    lead(last_session, 1) over(partition by user order by seconds) next_is_last 
from (
select 
    user, 
    ts, 
    ifnull(seconds - prev_seconds > 10, true) new_session, 
    ifnull(next_seconds - seconds > 10, true) last_session 
from (
select 
    user, 
    ts, 
    seconds, 
    lag(seconds, 1) over(partition by user order by seconds) prev_seconds, 
    lead(seconds, 1) over(partition by user order by seconds) next_seconds 
from 
(select user, ts, integer(ts/1000000) seconds from 
(select 'user_a' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_b' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:13') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:19') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:28') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:15') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:19') ts)))) 
where new_session or last_session) 
where new_session 

Это приводит к:

Row user ts      duration  
1 user_a 2015-06-15 14:12:12 UTC 16 
2 user_a 2015-06-15 19:32:15 UTC 4 
3 user_b 2015-06-15 14:12:12 UTC 0 
+0

Спасибо за Ваш ответ. Возможно, я описал свой вопрос непонятным. Вместо того, чтобы подсчитывать каждый интервал доступа в качестве сеанса, я хочу конкатрировать все непрерывные интервалы в один сеанс, если они не нарушены интервалом таймаута. Поэтому 'user_a' в моем примере имеет только два сеанса. Первые три интервала объединены в один сеанс, который длится 16 секунд. –

+0

Если вы запустите запрос выше - вы получите именно тот результат, который вы описали - user_a будет иметь два сеанса. –

+0

О, извините, я выполнил только подзапрос верхнего запроса. Вы правы, для user_a есть две сессии. Но мне действительно нужны длительности. Мне удалось генерировать каждый интервал доступа и являются ли они началом сеанса, но я не знаю, как суммировать продолжительность каждого сеанса. Любая идея? –

0

Ok, просвещенный Mosha's answer, я попытался это решение. Ключевыми моментами являются:

  1. использовать функцию окна в папку таблицы.
  2. исключить интервалы между началом и окончанием сеанса.
  3. использовать функцию окна снова, чтобы рассчитать продолжительность.

Вот сценарий:

select user, 
    case 
    when not new_session and end_of_session then seconds - start_time 
    when end_of_session and end_of_session then 0 
    end as duration, 
    case 
    when not new_session and end_of_session then start_time 
    when new_session and end_of_session then seconds 
    end as session_start, 
    seconds as session_end from 
(select *, lag(seconds, 1) over (partition by user order by seconds, prev_seconds) as start_time from 
(select user, seconds , new_session, ifnull(end_session_temp, true) end_of_session, prev_seconds from 
(select user, seconds , new_session, prev_seconds, lead(new_session, 1) over (partition by user order by seconds, prev_seconds) as end_session_temp from 
(select user, seconds, new_session, prev_seconds from 
(select user, seconds, prev_seconds, ifnull(seconds - prev_seconds > 10, true) new_session from 
(select user, ts, seconds, lag(seconds, 1) over(partition by user order by seconds) as prev_seconds from 
(select user, ts, integer(ts/1000000) seconds from 
(select 'user_a' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_b' user, timestamp('2015-06-15 14:12:12') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:13') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:19') ts), 
(select 'user_a' user, timestamp('2015-06-15 14:12:28') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:15') ts), 
(select 'user_a' user, timestamp('2015-06-15 19:32:19') ts)))))) 
where (new_session or end_session_temp is null or end_session_temp))) 
where not (new_session and not end_of_session) 

Выход:

Row   user  duration session_start session_end 
1   user_b  0   1434377532  1434377532 
2   user_a  16   1434377532  1434377548 
3   user_a  4   1434396735  1434396739 
Смежные вопросы