Как рассчитать коэффициент выживаемости в SQL? - PullRequest
0 голосов
/ 06 ноября 2019

(диалект может быть Vertica, Impala или Databricks)

Я пытаюсь рассчитать коэффициент выживаемости для пользователей от 0, 1 ... до 7 дней. ,Я буду относиться ко всем пользователям на определенную дату как к d0 (независимо от того, являются ли они новыми или старыми), и посмотрим, сколько из них вернется к d1, d2 и т. Д. Представьте, что у нас есть следующие данные:

user | login_date
-----------------
001  | 2019-11-01
002  | 2019-11-01
003  | 2019-11-01
004  | 2019-11-01
005  | 2019-11-01
001  | 2019-11-02
003  | 2019-11-02
004  | 2019-11-02
006  | 2019-11-02
007  | 2019-11-02
002  | 2019-11-03
003  | 2019-11-03
004  | 2019-11-03
005  | 2019-11-03
008  | 2019-11-03
001  | 2019-11-04
002  | 2019-11-04
006  | 2019-11-04
007  | 2019-11-04
009  | 2019-11-04

И я хотел бы видеть что-то вроде этого:

date      |d0 |d1 |d2 |d3
--------------------------
2019-11-01| 5 | 3 | 4 | 2
2019-11-02| 5 | 2 | 3 | 
2019-11-03| 5 | 1
2019-11-04| 5

Итак, вы можете видеть, что d0 равно 5 (даже если некоторые пользователи уже заходили в систему), и, например, у нас есть 001, 003, 004, 006, 007 на 2019-11-02, и 2 из них вернулись на следующий день.

Теперь я разработал запрос, который близок к моей цели, но это не то же самое.

WITH cte1 AS (
    SELECT
        user, 
        login_date,
        FIRST_VALUE(login_date) OVER (PARTITION BY user ORDER BY login_date) AS first_login_day,
        DATEDIFF(login_date, first_login_day) AS days_since_first_play
    FROM
        table
)
SELECT
    first_login_day,
    SUM(CASE WHEN days_since_first_play = 0 THEN 1 ELSE 0 END) AS d0,
    SUM(CASE WHEN days_since_first_play = 1 THEN 1 ELSE 0 END) AS d1,
    SUM(CASE WHEN days_since_first_play = 2 THEN 1 ELSE 0 END) AS d2,
    SUM(CASE WHEN days_since_first_play = 3 THEN 1 ELSE 0 END) AS d3,
    SUM(CASE WHEN days_since_first_play = 4 THEN 1 ELSE 0 END) AS d4,
    SUM(CASE WHEN days_since_first_play = 5 THEN 1 ELSE 0 END) AS d5,
    SUM(CASE WHEN days_since_first_play = 6 THEN 1 ELSE 0 END) AS d6,
    SUM(CASE WHEN days_since_first_play = 7 THEN 1 ELSE 0 END) AS d7
FROM
    cte1
GROUP BY
    first_login_day
ORDER BY
    first_login_day

Проблема с запросом состоит в том, что он удаляет старых игроков с даты, которую я смотрю . Например, при использовании тех же данных, поскольку 001, 003, 004 уже вошли в систему в 2019-11-01, значение d0 для 2019-11-02 будет равно 2 вместо 5. Так чтозапрос работает, только если я смотрю ТОЛЬКО на новых пользователей.

Мне интересно, могу ли я изменить запрос для достижения желаемого? Заранее спасибо ~~

Ответы [ 2 ]

1 голос
/ 14 ноября 2019

Несколько самоотключенных соединений и различного количества пользователей дадут такой результат.

SELECT t0.login_date,
COUNT(distinct t0.user) as d0,
COUNT(distinct t1.user) as d1,
COUNT(distinct t2.user) as d2,
COUNT(distinct t3.user) as d3
FROM table t0
LEFT JOIN table t1 
  ON t1.user = t0.user
 AND t1.login_date = t0.login_date + 1
LEFT JOIN table t2 
  ON t2.user = t0.user
 AND t2.login_date = t0.login_date + 2
LEFT JOIN table t3 
  ON t3.user = t0.user
 AND t3.login_date = t0.login_date + 3
GROUP BY t0.login_date
ORDER BY t0.login_date

Но если логин_дате нужно подключить?
Тогда просто измените критерии JOIN следующим образом:

FROM table t0
LEFT JOIN table t1 
  ON t1.user = t0.user
 AND t1.login_date = t0.login_date + 1
LEFT JOIN table t2 
  ON t2.user = t1.user
 AND t2.login_date = t1.login_date + 1
LEFT JOIN table t3 
  ON t3.user = t2.user
 AND t3.login_date = t2.login_date + 1
1 голос
/ 14 ноября 2019

Вот, по общему признанию, уродливый способ сделать это. Идея состоит в том, чтобы пометить каждый user_id, если он является возвращающим в день плюс один, день плюс два и т. Д., А затем агрегировать по login_date. Хотелось бы увидеть более хороший способ сделать это.

with offsets as (
select a.user_id
    , a.login_date
    , case when b.login_date is not null then 1 else 0 end day_plus_one
    , case when c.login_date is not null then 1 else 0 end day_plus_two
    , case when d.login_date is not null then 1 else 0 end day_plus_three
from table a
    left join table b
        on b.user_id = a.user_id
        and b.login_date  = a.login_date+1
    left join table c
        on c.user_id = a.user_id
        and c.login_date  = a.login_date+2
    left join table d
        on d.user_id = a.user_id
        and d.login_date  = a.login_date+3
order by a.user_id, a.login_date
)
select 
    login_date
    , count(distinct user_id) day_zero_logins
    , sum(day_plus_one) day_one_logins
    , sum(day_plus_two) day_two_logins
    , sum(day_plus_three) day_three_logins
from offsets
group by login_date
order by login_date

verified that it works with OP sample data

...