SQL - как эффективно разделить записи на подгруппы на основе других значений внутри подгруппы? - PullRequest
1 голос
/ 26 мая 2020

У меня таблица выглядит как ниже. Записи упорядочены по user_id и event_time.

Row    User_ID    Event_Time    Event_Type    
1      1          2020-01-01    View
2      1          2020-01-02    Click
3      1          2020-01-03    Purchase
4      2          2020-02-01    View
5      2          2020-02-02    Click
6      2          2020-02-03    View
7      2          2020-02-04    Purchase
8      2          2020-02-11    View
9      2          2020-02-12    Purchase
10     2          2020-02-21    View
11     2          2020-02-22    Click
12     2          2020-02-23    Purchase
13     2          2020-02-27    View
14     2          2020-02-28    Click
15     3          2020-03-01    View
16     3          2020-03-02    Purchase
...

Я хочу добавить новый столбец с именем Path, чтобы категоризировать события, не связанные с покупками. Каждое событие, не связанное с покупкой, пользователя «принадлежит» к событию немедленной покупки того же пользователя , которое произошло сразу после этого, то есть их можно рассматривать как подгруппу. В каждой подгруппе:

  • первое событие, не связанное с покупкой, - Introducer (строки 1, 4, 10)
  • последнее событие отсутствия покупки Closer (Ряд 2, 6, 11)
  • все события, не связанные с покупкой, между Introducer и Closer: Influencer (Ряд 5)
  • если с событием покупки сгруппировано только одно событие, не связанное с покупкой, то событие без покупки: Только (строки 8, 15)
  • заполните NULL в событиях покупки (строка 3, 7, 9, 12, 16)
  • заполните NULL, если событие, не связанное с покупкой, не относится ни к какому событию покупки (строки 13, 14)

Таким образом, таблица должна выглядеть вот так после добавления столбца:

Row    User_ID    Event_Time    Event_Type    Path
1      1          2020-01-01    View          Introducer
2      1          2020-01-02    Click         Closer
3      1          2020-01-03    Purchase      NULL
4      2          2020-02-01    View          Introducer
5      2          2020-02-02    Click         Influencer
6      2          2020-02-03    View          Closer
7      2          2020-02-04    Purchase      NULL
8      2          2020-02-11    View          Only
9      2          2020-02-12    Purchase      NULL
10     2          2020-02-21    View          Introducer
11     2          2020-02-22    Click         Closer
12     2          2020-02-23    Purchase      NULL
13     2          2020-02-27    View          NULL
14     2          2020-02-28    Click         NULL
15     3          2020-03-01    View          Only
16     3          2020-03-02    Purchase      NULL
...

Решение будет простым, если я сделаю самостоятельное присоединение и добавлю новый столбец, чтобы помочь определить, в какое время была последняя покупка пользователя для каждого события. Однако у меня более 100 миллионов записей, и самосоединение недостаточно эффективно. Время казни истекло. Итак, мой вопрос: есть ли более эффективный способ добавить этот новый столбец? Я подумываю использовать коррелированный запрос, но не могу осмыслить его.

Ответы [ 2 ]

2 голосов
/ 26 мая 2020

Это аналогичный подходу для Nick, но я думаю, что logi c проще:

WITH e AS (
      SELECT e.*,
             SUM(CASE WHEN Event_Type = 'Purchase' THEN 1 ELSE 0 END) OVER
                 (PARTITION BY User_ID ORDER BY Event_Time DESC) AS grp
      FROM events e
     ),
     en as (
      SELECT e.*,
             COUNT(*) OVER (PARTITION BY user_id, grp) as cnt,
             ROW_NUMBER() OVER (PARTITION BY user_id, grp ORDER BY Event_Time) as seqnum
      FROM e
     )
SELECT en.*,
       (CASE WHEN grp = 0                   -- no purchase event
             THEN NULL 
             WHEN Event_Type = 'Purchase'   -- the event itself
             THEN NULL
             WHEN seqnum = 1 AND cnt = 2    -- the special case of "ONLY" 
             THEN 'Only'
             WHEN seqnum = 1                -- The first event
             THEN 'Introducer'
             WHEN seqnum = cnt - 1          -- The penultimate event
             THEN 'Closer'
             ELSE 'Influencer'
        END) as Path
FROM en
ORDER BY User_ID, Event_Time;

В частности, подзапрос во внешнем запросе не нужен. grp = 0 находит последнюю группу событий, для которой может не быть покупки. Я также думаю, что проще написать logi c с точки зрения общего количества событий и последовательного счетчика.

Здесь - это скрипт db <>.

1 голос
/ 26 мая 2020

Если вы используете СУБД, поддерживающую оконные функции, вы можете использовать пару CTE, чтобы сначала разделить строки на разные покупки, затем найти номер строки относительно каждой из этих покупок и, наконец, вычислить Path на основе заданных вами условий:

WITH purchases AS (
  SELECT "Row", User_ID, Event_Time, Event_Type,
         COALESCE(SUM(CASE WHEN Event_Type = 'Purchase' THEN 1 ELSE 0 END) OVER
           (PARTITION BY User_ID ORDER BY Event_Time ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS pnum
  FROM events
),
prows AS (
  SELECT "Row", User_ID, Event_Time, Event_Type, pnum,
         ROW_NUMBER() OVER (PARTITION BY User_ID, pnum ORDER BY Event_Time) AS rn,
         ROW_NUMBER() OVER (PARTITION BY User_ID, pnum ORDER BY Event_Time DESC) AS drn
  FROM purchases
)
SELECT "Row", User_ID, Event_Time, Event_Type,
       CASE WHEN Event_Type = 'Purchase' OR
                 NOT EXISTS (SELECT * 
                             FROM prows r2 
                             WHERE r2.User_ID = r1.User_ID
                               AND r2.pnum = r1.pnum
                               AND r2.Event_Type = 'Purchase') THEN NULL
            WHEN rn = 1 AND drn = 2 THEN 'Only'
            WHEN rn = 1 THEN 'Introducer'
            WHEN drn = 2 THEN 'Closer'
            ELSE 'Influencer'
       END AS Path
FROM prows r1
ORDER BY User_ID, Event_Time

Вывод:

Row     User_ID     Event_Time  Event_Type  Path
1       1           2020-01-01  View        Introducer
2       1           2020-01-02  Click       Closer
3       1           2020-01-03  Purchase    (null)
4       2           2020-02-01  View        Introducer
5       2           2020-02-02  Click       Influencer
6       2           2020-02-03  View        Closer
7       2           2020-02-04  Purchase    (null)
8       2           2020-02-11  View        Only
9       2           2020-02-12  Purchase    (null)
10      2           2020-02-21  View        Introducer
11      2           2020-02-22  Click       Closer
12      2           2020-02-23  Purchase    (null)
13      2           2020-02-27  View        (null)
14      2           2020-02-28  Click       (null)
15      3           2020-03-01  View        Only
16      3           2020-03-02  Purchase    (null)

SQL Демонстрация сервера на SQLFiddle . Тот же запрос будет выполняться для PostgreSQL и Oracle.

...