<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: postgresql</title>
<link>http://test.leftjoin.ru/tags/postgresql/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Три способа рассчитать накопленную сумму в SQL</title>
<guid isPermaLink="false">125</guid>
<link>http://test.leftjoin.ru/all/sql-running-total/</link>
<comments>http://test.leftjoin.ru/all/sql-running-total/</comments>
<description>
&lt;p&gt;Расчет накопленной (или кумулятивной, что то же самое) суммы SQL — это очень распространенный запрос, который часто используют в анализе финансов, динамики прибыли и прочих показателей компании. В сегодняшней статье вы узнаете, что такое накопленная сумма и как можно написать SQL-запрос для ее вычисления.&lt;/p&gt;
&lt;p&gt;Если вы вдруг являетесь начинающим пользователем SQL, то давайте, как в школьной задаче, поймем, что нам дано и что нам необходимо найти. Накопленная сумма — это совокупная сумма предыдущих чисел в столбце. Давайте посмотрим на пример ниже, чтобы точно знать, какой результат мы ожидаем увидеть в итоге. Итак, существует таблица leftjoin.daily_sales_sample, в которой есть всего два столбца date и revenue. По столбцу revenue нам нужно рассчитать накопленную сумму и записать результат в отдельный столбец.&lt;/p&gt;
&lt;h3&gt;Что у нас есть?&lt;/h3&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Date&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Revenue&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h3&gt;Что мы хотим найти?&lt;/h3&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Date&lt;/td&gt;
&lt;td style="text-align: center"&gt;Revenue&lt;/td&gt;
&lt;td style="text-align: right"&gt;Cumulative Revenue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;td&gt;1200 ↓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;td&gt;2800↓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;12.11.2021&lt;/td&gt;
&lt;td style="text-align: left"&gt;800&lt;/td&gt;
&lt;td&gt;3600 ↓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;13.11.2021&lt;/td&gt;
&lt;td style="text-align: left"&gt;3000&lt;/td&gt;
&lt;td&gt;6600&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;На графике две этих переменных выглядят следующим образом:&lt;br /&gt;
&lt;img src="http://test.leftjoin.ru/pictures/sql_graph.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Итак, без лишних слов, давайте приступать к решению задачи.&lt;/p&gt;
&lt;h2&gt;Способ 1 — Идеальный —  Используем оконные функции&lt;/h2&gt;
&lt;p&gt;Итак, если в базе данных можно пользоваться оконными функциями, то жизнь хороша и прекрасна. С их помощью можно написать простой запрос, который будет суммировать значения из столбца revenue по мере увеличения даты и сразу вернет нам таблицу с кумулятивной суммой в столбце, который мы назвали total.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
	date,
	revenue,
	SUM(revenue) OVER (ORDER BY date asc) as total
FROM leftjoin.daily_sales_sample 
ORDER BY date;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Способ 2 — Хитрый — Решение без оконных функций&lt;/h2&gt;
&lt;p&gt;Вполне возможно, что вам понадобится решить такую задачу без использования оконных функций. К примеру, если вы используете MySQL (до 8 версии) или любую другую БД, в которой оконных функций нет. Тогда решение задачи чуть усложняется. Однако, вы ведь знаете, что нет ничего невозможного?&lt;br /&gt;
Чтобы провернуть все то же самое без оконных функций, нужно использовать INNER JOIN для присоединения таблицы к себе самой. Так, к каждой строке таблицы мы присоединяем строки, которые соответствуют всем предыдущим датам до текущей даты включительно. В нашем примере, для 10 ноября — 10 ноября, для 11 ноября — 10 и 11 ноября и так далее. Промежуточный запрос будет выглядеть вот так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT * 
FROM leftjoin.daily_sales_sample ds1 
INNER JOIN leftjoin.daily_sales_sample ds2 on ds1.date&amp;gt;=ds2.date
ORDER BY ds1.date, ds2.date;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;А его результат:&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Date 1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Revenue 1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Date 2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Revenue 2&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;10.11.2021&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;11.11.2021&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;12.11.2021&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;13.11.2021&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;А затем, нужно просуммировать прибыли, группируя их по каждой дате. Если собрать все в единый запрос, то он будет выглядеть вот так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
	ds1.date,
	ds1.revenue,
	SUM(ds2.revenue) as total
FROM leftjoin.daily_sales_sample ds1 
INNER JOIN leftjoin.daily_sales_sample ds2 on ds1.date&amp;gt;=ds2.date
GROUP BY ds1.date, ds1.revenue
ORDER BY ds1.date;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Способ 3 — Специфический — Решение с помощью массивов в ClickHouse&lt;/h2&gt;
&lt;p&gt;Если вы используете Clickhouse, то в этой системе есть специальная функция, которая может помочь рассчитать кумулятивную сумму. Для начала, нам нужно преобразовать все столбцы таблицы в массивы и рассчитать показатель «Moving Sum» для столбца revenue.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT groupArray(date) dates, groupArray(revenue) as revs, 
groupArrayMovingSum(revenue) AS total
FROM (SELECT date, revenue FROM leftjoin.daily_sales_sample
	  ORDER BY date)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;i&gt;Спасибо &lt;a href="https://t.me/unamedrus"&gt;Дмитрию Титову&lt;/a&gt; из Altinity за комментарий про сортировку в подзапросе&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Так, мы получим три массива значений:&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;b&gt;dates&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;revs&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;&lt;b&gt;total&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[’10.11.2021’,’11.11.2021’,’12.11.2021’,’13.11.2021’]&lt;/td&gt;
&lt;td style="text-align: center"&gt;[1200, 1600, 800, 300]&lt;/td&gt;
&lt;td style="text-align: right"&gt;[1200, 2800, 3600, 3900]&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Но три массива, которые записаны в ячейки — это не то, что мы хотим получить, хотя значения этих массивов уже абсолютно соответствуют искомому результату. Теперь массивы нужно привести обратно к табличному виду с помощью функции ARRAY JOIN.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT dates, revs, total FROM
(SELECT groupArray(date) dates, groupArray(revenue) as revs, 
groupArrayMovingSum(revenue) AS total
FROM (SELECT date, revenue FROM leftjoin.daily_sales_sample
	  ORDER BY date)) as t
ARRAY JOIN dates, revs, total;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Бонус — Оконные функции в Clickhouse&lt;/h2&gt;
&lt;p&gt;Если вам не хочется иметь дело с массивами, что иногда и правда бывает затратно по времени, то есть еще один вариант решения задачи.  Можно использовать оконные функции, например функцию &lt;a href="https://clickhouse.com/docs/en/sql-reference/functions/other-functions/"&gt;runningAccumulate(&lt;/a&gt;), которая суммирует  значения всех ячеек с первой до текущей.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT date, runningAccumulate(revenue)
  FROM 
  (
    SELECT date, sumState(revenue) AS revenue
    FROM leftjoin.daily_sales_sample
    GROUP BY date 
    ORDER BY date ASC
  )
ORDER BY date&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если вы столкнетесь с необходимостью рассчитать кумулятивную сумму в SQL, то теперь вы сможете решить эту задачу, в какой бы системе управления баз данных ни была организована работа :)&lt;/p&gt;
</description>
<pubDate>Fri, 10 Dec 2021 19:10:34 +0300</pubDate>
</item>

<item>
<title>Моделирование LTV в SQL</title>
<guid isPermaLink="false">115</guid>
<link>http://test.leftjoin.ru/all/modeling-ltv-with-sql/</link>
<comments>http://test.leftjoin.ru/all/modeling-ltv-with-sql/</comments>
<description>
&lt;p&gt;У большинства игровых и мобильных компаний имеется кривая Retention, ранее мы писали о том, &lt;a href="http://test.leftjoin.ru/all/retention-rate/"&gt;что такое Retention и как его посчитать&lt;/a&gt;. Вкратце — это метрика, которая позволяет понять насколько хорошо продукт вовлекает пользователей в ежедневное использование. А ещё при помощи Retention и ARPDAU можно посчитать LTV (Lifetime Value), пожизненный доход с одного пользователя. Зная средний доход с пользователя за день и кривую Retention мы можем смоделировать ее и спрогнозировать LTV.&lt;/p&gt;
&lt;p class="note"&gt;Для материала были взяты данные из одного реального игрового проекта. Нулевой день не отображен для того, чтобы видеть динамику в деталях&lt;/p&gt;
&lt;p&gt;В сегодняшнем материале мы подробно разберём, как смоделировать LTV для 180 дней при помощи SQL и просто линейной регрессии.&lt;/p&gt;
&lt;h2&gt;Как посчитать LTV?&lt;/h2&gt;
&lt;p&gt;В общем случае формула LTV выглядит как ARPDAU умноженное на Lifetime — время жизни пользователя в проекте.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart.png" width="223" height="19" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Посмотрим на классический график Retention:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.54.53.png" width="942" height="413" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Lifetime — это площадь фигуры под Retention:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.55.13.png" width="923" height="410" alt="" /&gt;
&lt;/div&gt;
&lt;p class="note"&gt;Откуда взялись интегралы и площади можно подробнее узнать в &lt;a href="https://gdcuffs.com/ltv-integrals-and-areas/"&gt;этом материале&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Значит, чтобы посчитать Lifetime, нужно взять интеграл от функции удержания по времени. Формула приобретает следующий вид:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-2.png" width="198" height="28" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Для описания кривой Retention лучше всего подходит степенная функция a*x^b. Вот как она выглядит в сравнении с кривой Retention:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.58.19.png" width="802" height="403" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;При этом x — номер дня, a и b — параметры функции, которую мы построим при помощи линейной регрессии. Регрессия появилась неслучайно — эту степенную функцию можно привести к виду линейной функции, логарифмируя:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-4.png" width="64" height="18" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-5.png" width="154" height="20" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-6.png" width="156" height="19" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;ln(a) — intercept, b — slope. Остаётся найти эти параметры — в линейной регрессии для этого используют метод наименьших квадратов. Lifetime — кумулятивная сумма прогноза за 180 дней. Посчитав её, остаётся умножить Lifetime на ARPDAU и получим LTV за 180 дней.&lt;/p&gt;
&lt;h2&gt;Строим LTV&lt;/h2&gt;
&lt;p&gt;Перейдём к практике. Для всех расчётов мы использовали данные одной игровой компании и СУБД PostgreSQL — в ней уже реализованы функции поиска параметров для линейной регрессии. Начнём с построения Retention: соберём общее количество пользователей в период с 1 марта по 1 апреля 2021 года — мы изучаем активность за один месяц:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;--общее количество юзеров в когорте
with cohort as (
    select count(distinct id) as total_users_of_cohort
    from users
    where date(registration) between date '2021-03-01' and date '2021-03-30'
),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь посмотрим, как ведут себя эти пользователи в последующие 90 дней:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;--количество активных юзеров на 1ый день, 2ой, 3ий и тд. из когорты
active_users as (
    select date_part('day', activity.date - users.registration) as activity_day, 
               count(distinct users.id) as active_users_of_day
    from activity
    join users on activity.user_id = users.id
    where date(registration) between date '2021-03-01' and date '2021-03-30' 
    group by 1
    having date_part('day', activity.date - users.registration) between 1 and 90 --берем только первые 90 дней, остальные дни предсказываем.
),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Кривая Retention — отношение количества активных пользователей к размеру когорты текущего дня. В нашем случае она выглядит так:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.54.53.png" width="942" height="413" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;По данным кривой посчитаем параметры для линейной регрессии. regr_slope(x, y) — функция для вычисления наклона регрессии, regr_intercept(x, y) — функция для вычисления перехвата по оси Y. Эти функции являются стандартными &lt;a href="https://www.postgresql.org/docs/9.4/functions-aggregate.html"&gt;агрегатными функциями в PostgreSQL&lt;/a&gt; и для известных X и Y по методу наименьших квадратов.&lt;/p&gt;
&lt;p&gt;Вернёмся к нашей формуле — мы получили линейное уравнение, и хотим найти коэффициенты линейной регрессии. Перехват по оси Y и коэффициент наклона можем найти по дефолтным для PostgreSQL функциям. Получается:&lt;/p&gt;
&lt;p class="note"&gt;Подробнее о том, как работают функции intercept(x, y) и slope(x, y) можно почитать в &lt;a href="https://www.mathsisfun.com/data/least-squares-regression.html"&gt;этом мануале&lt;/a&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-6.png" width="156" height="19" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-10.png" width="217" height="21" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Из свойства натурального логарифма следует, что:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-11.png" width="165" height="25" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Наклон считаем аналогичным образом:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/chart-12.png" width="157" height="21" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Эти же вычисления запишем в подзапрос для расчёта коэффициентов регрессии:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;--рассчитываем коэффициенты регрессии
coef as (
    select exp(regr_intercept(ln(activity), ln(activity_day))) as a, 
                regr_slope(ln(activity), ln(activity_day)) as b
    from(
                select activity_day,
                            active_users_of_day::real / total_users_of_cohort as activity
                from active_users 
                cross join cohort order by activity_day 
            )
),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И получим прогноз на 180 дней, подставив параметры в степенную функцию, описанную ранее. Заодно посчитаем Lifetime — кумулятивную сумму спрогнозированных данных. В подзапросе coef мы получим только два числа — параметр наклона и перехвата. Чтобы эти параметры были доступны каждой строке подзапроса lt, делаем cross join к coef:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;lt as(
    select generate_series as activity_day,
               active_users_of_day::real/total_users_of_cohort as real_data,
               a*power(generate_series,b) as pred_data, 	 
               sum(a*power(generate_series,b)) over(order by generate_series) as cumulative_lt
    from generate_series(1,180,1)
    cross join coef
    join active_users on generate_series = activity_day::int
),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Сравним прогноз на 180 дней с Retention:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.30.59.png" width="936" height="425" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Наконец, считаем сам LTV — Lifetime, умноженный на ARPDAU. В нашем случае ARPDAU равняется $83.7:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;select cumulative_lt as LT,
           cumulative_lt * 83.7 as LTV
from lt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наконец, построим график LTV на 180 дней:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-08-13--15.51.05.png" width="852" height="416" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Весь запрос:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;--общее количество юзеров в когорте
with cohort as (
    select count(*) as total_users_of_cohort
    from users
    where date(registration) between date '2021-03-01' and date '2021-03-30'
),
--количество активных юзеров на 1ый день, 2ой, 3ий и тд. из когорты
active_users as (
    select date_part('day', activity.date - users.registration) as activity_day, 
               count(distinct users.id) as active_users_of_day
    from activity
    join users on activity.user_id = users.id
    where date(registration) between date '2021-03-01' and date '2021-03-30' 
    group by 1
    having date_part('day', activity.date - users.registration) between 1 and 90 --берем только первые 90 дней, остальные дни предсказываем.
),
--рассчитываем коэффициенты регрессии
coef as (
    select exp(regr_intercept(ln(activity), ln(activity_day))) as a, 
                regr_slope(ln(activity), ln(activity_day)) as b
    from(
                select activity_day,
                            active_users_of_day::real / total_users_of_cohort as activity
                from active_users 
                cross join cohort order by activity_day 
            )
),
lt as(
    select generate_series as activity_day,
               active_users_of_day::real/total_users_of_cohort as real_data,
               a*power(generate_series,b) as pred_data, 	 
               sum(a*power(generate_series,b)) over(order by generate_series) as cumulative_lt
    from generate_series(1,180,1)
    cross join coef
    join active_users on generate_series = activity_day::int
),
select cumulative_lt as LT,
            cumulative_lt * 83.7 as LTV
from lt&lt;/code&gt;&lt;/pre&gt;</description>
<pubDate>Mon, 16 Aug 2021 09:32:37 +0300</pubDate>
</item>


</channel>
</rss>