Как преобразовать этот запрос MySQL для запуска на Snowke / MPP - PullRequest
0 голосов
/ 18 мая 2018

Фон

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

Проблема

У нас есть идентификатор клиента id и метка времени сеанса event_datetime.

Для каждого клиента мне нужно интерпретировать каждый сеанс как действительный или недействительный согласно следующему определению:

  • срок действия действующего сеанса истекает через 30 минут.
  • сеанс действителен, если он не произошел во время более раннего действительного сеанса.

Альтернативное определение тривиально для вычисления: сеанс действителен, если он был 30минут с последнего сеанса.Но это не то, что мне нужно здесь.

Так, например:

2018-01-01 00:00:00 <-- valid
2018-01-01 00:15:00 <-- invalid
2018-01-01 00:31:00 <-- valid
2018-01-01 01:14:00 <-- valid
2018-01-01 01:17:00 <-- invalid
2018-01-01 01:25:00 <-- invalid
2018-01-01 01:43:00 <-- invalid
2018-01-01 01:45:00 <-- valid

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

То, что я пытался

Я пытался придумать что-то, используя оконные функции, объединения, не существует, ноизо всех сил, чтобы найти решение.Например, выполнение промежуточной суммы различий во времени сеанса казалось многообещающим, но я не мог придумать, как заставить накопленную сумму обнуляться после достижения 30 минут.Я знаю, что мог бы заказать сеансы каждого клиента и выполнить цикл (так, чтобы максимальные итерации были максимальным числом сеансов для одного клиента), но пытался избежать этого.

Пример данных и решение MySQL

Ниже приведено решение, использующее mysql.Оба определения рассчитаны (по истечении 30 минут и 30 минут).

DROP TABLE IF EXISTS work.test;
CREATE TABLE work.test (id INT, event_datetime DATETIME);
INSERT INTO work.test
VALUES (123456789, '2017-12-08 15:24:29.297000000'),
    (123456789, '2017-12-08 15:25:42.510000000'),
    (123456789, '2017-12-08 15:28:49.023000000'),
    (123456789, '2017-12-10 07:23:49.693000000'),
    (123456789, '2017-12-10 07:25:03.487000000'),
    (123456789, '2017-12-10 07:35:52.613000000'),
    (123456789, '2017-12-10 07:45:52.613000000'),
    (123456789, '2017-12-10 07:55:52.613000000'),
    (123456789, '2017-12-10 08:05:52.613000000'),
    (123456789, '2017-12-10 15:55:24.070000000'),
    (123456789, '2017-12-10 15:55:57.063000000'),
    (123456789, '2017-12-10 15:56:37.633000000'),
    (123456789, '2017-12-17 09:00:41.543000000'),
    (123456789, '2017-12-17 09:02:13.187000000'),
    (123456789, '2017-12-17 09:02:47.370000000'),
    (123456789, '2017-12-17 09:03:29.843000000'),
    (123456789, '2017-12-17 09:03:56.667000000'),
    (123456789, '2017-12-17 09:06:12.493000000'),
    (123456789, '2017-12-17 09:07:26.113000000');


SELECT
    @last_session_datetime AS last_session_datetime,
    @diff := timestampdiff(MINUTE, @last_session_datetime, s.event_datetime) AS diff,
    if(@diff IS NULL OR @diff >= 30, 'valid', 'not valid') AS valid_30_minute_lapse,
    @last_visit_datetime := if(@curr_customer_id = s.id AND timestampdiff(MINUTE, @last_visit_datetime, s.event_datetime) < 30, @last_visit_datetime, s.event_datetime) AS last_visit_datetime,
    if(@last_visit_datetime = s.event_datetime, 'valid', 'not valid') AS valid_30_minute_expiration,
    @curr_customer_id := s.id,
    id,
    event_datetime,
    @last_session_datetime := s.event_datetime
FROM work.test s
JOIN (
         SELECT
             @curr_customer_id := 0,
             @last_visit_datetime := '1900-01-01',
             @last_session_datetime := NULL) a
ORDER BY s.id, s.event_datetime

В этих примерах данных сеанс 2017-12-10 07:55:53 будет действителен по истечении 30 минут, но недействителен по истечении 30-минутного промежутка.Прошло всего 10 минут после предыдущего сеанса, но прошло более 30 минут с момента последнего подтвержденного сеанса.

Ответы [ 2 ]

0 голосов
/ 18 мая 2018

Табличная функция Javascript UDF в Snowflake идеально подходит для этого типа запросов.По сути, вы можете определить свою собственную оконную функцию, которая отслеживает поток дат по порядку и выводит значения «действительные» и «недействительные» на основе ранее увиденного «действительного» значения.(UDF-таблицы с Javascript Table-Valued задокументированы здесь ).

Ниже приведен пример кода для этого:

CREATE OR REPLACE FUNCTION classify (ts string)
RETURNS table (valid string)
LANGUAGE JAVASCRIPT
STRICT
IMMUTABLE
AS '
{


 initialize: function (argumentInfo, context) {
    validStart = Date.parse("0000-00-00");
 },


 processRow: function (row, rowWriter, context) {
    var thisDate = Date.parse(row.TS);
    var minsDiff = (thisDate - validStart) / (1000 * 60);
    if (minsDiff < 30) {
       rowWriter.writeRow({VALID:  "invalid"});
    }
    else {
       validStart = thisDate;
       rowWriter.writeRow({VALID:  "valid"})
    }

 },


 finalize: function (rowWriter, context) {/*...*/},

 }
 ';

Теперь вы можете вызывать эту функцию в каждой строкев вашем потоке следующим образом ... (предположительно, вы хотите, чтобы значение "действительное / недействительное" рассчитывалось сегментировано на основе групп идентификаторов):

select * from test,  
table(classify(event_datetime::string) 
      over (partition by id order by event_datetime));

При выполнении данных вашего образца это результат:

enter image description here

0 голосов
/ 18 мая 2018

Основная идея, казалось бы, захвачена подзапросом not exists:

select t.*
from work.test t
where not exists (select 1
                  from work.test t2
                  where t2.id = t.id and
                        t2.event_datetime > t.event_datetime - interval '30 minute' nd
                        t2.event_datetime < t.event_datetime
                 );
...