Как получить когортный анализ с помощью MySQL?

#mysql #sql

Вопрос:

Вот мой текущий запрос:

 SELECT DATEDIFF(created_at, '2020-07-01') DIV 6 period, 
       user_id FROM transactions
WHERE DATE(created_at) >= '2020-07-01' 
GROUP BY user_id, DATEDIFF(created_at, '2020-07-01') DIV 6
ORDER BY period
 

Он возвращает список пользователей, у которых была хотя бы одна транзакция за период (период === 6 дней). Вот упрощенное представление текущего выходного сигнала:

 // res_table
 -------- --------- 
| period | user_id |
 -------- --------- 
| 0      | 1111    |
| 0      | 2222    |
| 0      | 3333    |
| 1      | 7777    |
| 1      | 1111    |
| 2      | 2222    |
| 2      | 1111    |
| 2      | 8888    |
| 2      | 3333    |
 -------- --------- 
 

Теперь мне нужно знать, за какой период у скольких пользователей снова была хотя бы одна транзакция (с точки зрения маркетинга я пытаюсь представить коэффициент удержания с помощью когортной диаграммы). Поэтому вычисления должны выполняться в декартовом алгоритме; Например self-join ,!

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

  --------- --------- ------------ 
| periodX | periodY | percentage |
 --------- --------- ------------ 
| 0       | 0       | 100%       | -- it means 3 users exist in period 0 and logically all of them exist in period 0. So 3/3=100%
| 0       | 1       | 33%        | -- It means 3 users exist in period 0, and just 1 of them exist in period 1. So  1/3=33%
| 0       | 2       | 66%        | -- It means 3 user exists in period 0, and just 2 of them exist in period 2. So 2/3=66%
| 1       | 1       | 100%       | -- it means 1 user (only #777, actually #111 is ignored because it's duplicated in pervious periods) exists in period 1 and logically it exists in period 1. So 1/1=100%
| 1       | 2       | 0%         |
| 2       | 2       | 100%       |
 --------- --------- ------------ 
 

Можно ли это сделать чисто с помощью MySQL?

Ответ №1:

Вы можете использовать оконные функции:

 SELECT first_period, period, COUNT(*),
       COUNT(*) / SUM(COUNT(*)) OVER (PARTITION BY first_period) as ratio
FROM (SELECT DATEDIFF(created_at, '2020-07-01') DIV 6 period, 
            user_id,
            MIN(MIN(DATEDIFF(created_at, '2020-07-01') DIV 6) OVER (PARTITION BY user_id)) as first_period
     FROM transactions
     WHERE DATE(created_at) >= '2020-07-01' 
     GROUP BY user_id, DATEDIFF(created_at, '2020-07-01') DIV 6
    ) u
GROUP BY first_period, period
ORDER BY first_period, period;
 

Это не включает пропущенные периоды. Это немного хитростей, потому что вам нужно перечислить их все:

 with periods as (
      select 0 as period union all
      select 1 as period union all
      select 2 as period
     )
select p1.period, p2.period, COUNT(u.user_id)
from periods p1 join
     periods p2
     on p1.period <= p2.period left join
     (SELECT DATEDIFF(created_at, '2020-07-01') DIV 6 period, 
             user_id,
             MIN(MIN(DATEDIFF(created_at, '2020-07-01') DIV 6) OVER (PARTITION BY user_id)) as first_period
      FROM transactions
      WHERE DATE(created_at) >= '2020-07-01' 
      GROUP BY user_id, DATEDIFF(created_at, '2020-07-01') DIV 6
     ) u
     ON p1.period = u.first_period AND p2.period = u.period
GROUP BY p1.period, p2.period;
 

Комментарии:

1. #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as first_period FROM transactions WHERE DATE(created_at) >= '202' at line 5

2. @MartinAJ . . . Это была опечатка.

3. #1055 - Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'dbname.transactions.created_at' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

4. @MartinAJ . . . На этот раз синтаксической ошибки нет: dbfiddle.uk/.

5. Вы действительно хорошо разбираетесь в SQL .. На один уровень выше, чем у всех .. Полностью владеет понятиями и синтаксисом … Идеально ..!