Получение значений из первой строки, последней строки и агрегирование в MySQL оконной функции - PullRequest
0 голосов
/ 19 июня 2020

Для маркетингового анализа мне нужно предоставить данные о первой и последней точке взаимодействия, а также об общем количестве взаимодействий с нашим веб-сайтом.

Упрощенная версия нашей interaction таблицы выглядит так :

create table interaction (
    id varchar(36) primary key,
    session_id varchar(36) not null,
    timestamp timestamp(3) not null,
    utm_source varchar(255) null,
    utm_medium varchar(255) null
)

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

with interaction_ordered as (
    select *, 
           row_number() over (partition by session_id order by timestamp asc) as row_num_asc,
           row_number() over (partition by session_id order by timestamp desc) as row_num_desc
    from interaction
)

select first_interaction.session_id as session_id,
       first_interaction.timestamp as session_start,
       timestampdiff(SECOND, first_interaction.timestamp, last_interaction.timestamp) as session_duration,
       count(*) as interaction_count,
       first_interaction.utm_source as first_touchpoint,
       last_interaction.utm_source as last_touchpoint,
       last_interaction.utm_medium as last_medium
from interaction_ordered as interaction
join interaction_ordered as first_interaction using (session_id)
join interaction_ordered as last_interaction using (session_id)
where first_interaction.row_num_asc = 1 and last_interaction.row_num_desc = 1
group by session_id
having session_start between ? - interval 1 day and ? + interval 1 day

В настоящее время мы наблюдаем, что время выполнения масштабируется примерно линейно с нашими данными, что вскоре станет невозможным для вычислений.

Альтернативная идея:

select session_id,
       min(timestamp) as session_start,
       timestampdiff(
           SECOND,
           min(timestamp),
           max(timestamp)
       ) as session_duration,
       count(*) as interaction_count,
       first_value(utm_source) over (partition by session_id order by timestamp) as first_touchpoint,
       first_value(utm_source) over (partition by session_id order by timestamp desc) as last_touchpoint,
       first_value(utm_medium) over (partition by session_id order by timestamp desc) as last_medium
from interaction
group by session_id
having session_start between ? - interval 1 day and ? + interval 1 day

, но в наших экспериментах мы никогда не видели, чтобы второй запрос был завершен. Следовательно, мы не на 100% уверены, что он дает такие же результаты.

Мы пробовали индексы на timestamp и (session_id, timestamp), но, согласно EXPLAIN, это не изменило план запроса *. 1020 *

Есть ли какой-нибудь быстрый способ получить отдельные свойства из первой и последней записи на идентификатор сеанса плюс счетчик на идентификатор сеанса? Обратите внимание, что в нашем реальном примере нас интересуют другие параметры, такие как utm_source и utm_medium.

EDIT

Примеры данных:

insert into interaction values
('a', 'session_1', '2020-06-15T12:00:00.000', 'search.com', 'search'),
('b', 'session_1', '2020-06-15T12:01:00.000', null, null),
('c', 'session_1', '2020-06-15T12:01:30.000', 'social.com', 'social'),
('d', 'session_1', '2020-06-15T12:02:00.250', 'ads.com', 'ads'),

('e', 'session_2', '2020-06-15T14:00:00.000', null, null),
('f', 'session_2', '2020-06-15T14:12:00.000', null, null),
('g', 'session_2', '2020-06-15T14:25:00.000', 'social.com', 'social'),

('h', 'session_3', '2020-06-16T12:05:00.000', 'ads.com', 'ads'),
('i', 'session_3', '2020-06-16T12:05:01.000', null, null),

('j', 'session_4', '2020-06-15T12:00:00.000', null, null),
('k', 'session_5', '2020-06-15T12:00:00.000', 'search.com', 'search');

Ожидаемый результат:

session_id, session_start, session_duration, interaction_count, first_touchpoint, last_touchpoint, last_medium
session_1, 2020-06-15T12:00:00.000, 120, 4, search.com, ads.com, ads
session_2, 2020-06-15T14:00:00.000, 1500, 3, null, social.com, social
session_3, 2020-06-16T12:05:00.000, 1, 2, ads.com, null, null
session_4, 2020-06-15T12:00:00.000, 0, 1, null, null, null
session_5, 2020-06-15T12:00:00.000, 0, 1, search.com, search.com, search

Я заметил, что мой второй запрос не дает ожидаемого результата. Вместо last_touchpoint и last_medium заполняется первое значение. Я пробовал

  • first_value(utm_source) over (partition by session_id order by timestamp desc) as last_touchpoint, и
  • last_value(utm_source) over (partition by session_id order by timestamp range between unbounded preceding and unbounded following) as last_touchpoint,

Ответы [ 2 ]

1 голос
/ 19 июня 2020

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

select s.*
from (select i.*,
             min(timestamp) over (partition by session_id) as session_start,
             count(*) over (partition by session_id) as interaction_count,
             first_value(utm_source) over (partition by session_id order by timestamp) as first_touchpoint,
             first_value(utm_source) over (partition by session_id order by timestamp desc) as last_touchpoint,
             first_value(utm_medium) over (partition by session_id order by timestamp desc) as last_medium
      from interaction i
      where timestamp between ? - interval 2 day and ? + interval 2 day
     ) s
where timestamp = session_start and
      session_start between ? - interval 1 day and ? + interval 1 day;

Использование first_value() должно возвращать ошибку - это нарушает правила «полной группы по», которые MySQL 8+ установлены по умолчанию. Неудивительно, что синтаксически неверный код не работает.

1 голос
/ 19 июня 2020
WITH cte AS ( SELECT *,
                     FIRST_VALUE(utm_source) OVER (PARTITION BY session_id ORDER BY `timestamp` ASC) first_touchpoint,
                     FIRST_VALUE(utm_source) OVER (PARTITION BY session_id ORDER BY `timestamp` DESC) last_touchpoint,
                     FIRST_VALUE(utm_medium) OVER (PARTITION BY session_id ORDER BY `timestamp` DESC) last_medium
              FROM interaction
            )
SELECT session_id,
       MIN(`timestamp`) session_start,
       TIMESTAMPDIFF(SECOND, MIN(`timestamp`), MAX(`timestamp`)) session_duration,
       COUNT(*) interaction_count,
       ANY_VALUE( first_touchpoint ) first_touchpoint,
       ANY_VALUE( last_touchpoint ) last_touchpoint,
       ANY_VALUE( last_medium ) last_medium
FROM cte
GROUP BY session_id;

скрипка

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...