计算每月的通讯订阅者总数
Posted
技术标签:
【中文标题】计算每月的通讯订阅者总数【英文标题】:Count the number of total newsletter subscribers per month 【发布时间】:2018-01-05 23:00:45 【问题描述】:我现在已经摸索了好几个小时,试图弄清楚如何使用 Redshift统计每月的通讯订阅者总数。
计算的基础是跟踪每个用户操作的事件表,特别是他订阅还是取消订阅时事通讯。简化后看起来像这样:
+----------------------+---------+---------------+
| timestamp | user_id | action |
+----------------------+---------+---------------+
| 2017-01-01T12:10:31Z | 1 | subscribed |
| 2017-01-01T13:11:51Z | 2 | subscribed |
| 2017-01-01T13:15:53Z | 3 | subscribed |
| ... | ... | ... |
| 2017-02-17T09:42:33Z | 4 | subscribed |
| ... | ... | ... |
| 2017-03-15T16:59:13Z | 1 | unsubscribed |
| 2017-03-17T02:19:56Z | 2 | unsubscribed |
| 2017-03-17T05:33:05Z | 2 | subscribed |
| ... | ... | ... |
我喜欢总结每个月订阅时事通讯的用户数量加上已经订阅但没有退订的用户数量。在上面的示例中,我们将在一月份有 3 个用户,在二月份添加另一个用户,总共有 4 个订阅者。然后在三月份,我们失去了一位用户,而另一位用户只是暂时退订。我们三月份的总订阅人数是 3。
我正在寻找的最终结果是这样的:
+------------+-------------+
| month | subscribers |
+------------+-------------+
| 2017-01-01 | 3 |
| 2017-02-01 | 4 |
| 2017-03-01 | 3 |
| ... | ... |
任何想法是否以及如何使用 SQL 查询来解决(最好在 Redshift 或 Postgres 中工作)?
【问题讨论】:
重要的是要知道这是针对 redshift 还是 postgres,因为它们在高级级别完全不同!是哪一个?请删除一个标签? 【参考方案1】:解决办法是:
1) 创建一个存储日期的日历表(表中的一行是唯一的日期),请参阅this 问题中的更多信息。这对于大多数 BI 查询来说非常方便。
2) 编写包含以下步骤的查询:
2a) 基于订阅/取消订阅事件,为每个用户构建订阅状态的时间范围(首先使用lead
函数识别每个给定事件的下一个事件并选择必要的对)。如果用户只有一个订阅事件,请使用coalesce
将date_to
设置为当前日期。
2b) 将这些范围连接到日历表中,这样一行就是一个日期/用户
2c) 使用一种或另一种方法计算行数(唯一 ID、每日平均、每月的第一个日期、每月的最后一个日期)
查询将如下所示:
with
next_events as (
select
user_id
,"timestamp"::date as date_from
,action
,lead(timestamp) over (partition by user_id order by timestamp) ::date as date_to
,lead(action) over (partition by user_id order by timestamp) as next_action
from your_table
where action in ('subscribed','unsubscribed')
)
,ranges as (
select
user_id
,date_from
,coalesce(date_to,current_date) as date_to
from next_events
where (action='subscribed' and next_action='unsubscribed')
or (action='subscribed' and next_action is null)
)
,subscriber_days as (
select
t1.user_id
,t2.date
from ranges t1
join calendar t2
on t2.date between t1.date_from and t1.date_to
)
-- use whatever method needed to identify monthly N from daily N (first day, last day, average, etc.)
-- below is the unique count
select
date_trunc('month',date) as date
,count(distinct user_id) as subscribers
from subscriber_days
group by 1
order by 1
【讨论】:
感谢您的提示。我已经玩过这个查询了,通常它似乎工作。但它并不准确,如此 SQLFiddle 所示:sqlfiddle.com/#!17/b7ace/1 例如,March 似乎差了一个。我添加了一个基于其他一些测试来源的自定义日历生成功能。 @André 三月怎么了?它是 7,如果您在本月的任何时间点计算唯一订阅者,它似乎是基于数据的。用户 id=1 在月中退订但已计入 哦,我现在知道你是怎么算的了。是的,这是有道理的。你是绝对正确的。谢谢! 太棒了!请接受答案。您还可以先计算每日计数,然后对其进行平均,以获得该月任何时间点的平均用户计数。流失/增长越高,与独特计数的差异就越大(如果您的用户群每月完全刷新一次,则平均数将比独特计数低 2 倍) 我会接受这个作为答案有两个原因:1)它解释了采取的必要步骤,以便新手可以理解需要做什么,2)它提供了我可以手动验证的准确数字出乎我的意料。其他答案有细微的变化,我无法立即弄清楚。最后,我什至想出了自己的方法,只需将订阅表与自身连接起来,以确定每个给定月份谁订阅但尚未取消订阅。如果有人关心,我很乐意分享解决方案。【参考方案2】:您可以使用递归 CTE 来创建每个所需的月份。然后将订阅与取消订阅匹配(为简单起见,另一个 CTE)。请注意用于选择前 1 个匹配取消订阅的横向连接。最后,获取每个月不同 user_id 的计数。
这是 Postgres。 Here is the SQL Fiddle where you can run this, adjust the data set, etc.
WITH RECURSIVE months(start_date, end_date) AS (
select
timestamp '2017-01-01',/*change this date to adjust range*/
(date_trunc('MONTH', timestamp '2017-01-01') + INTERVAL '1 MONTH')::DATE/*change this date to adjust range*/
UNION ALL
SELECT
start_date + interval '1 month',
(date_trunc('MONTH', start_date + interval '1 month') + INTERVAL '1 MONTH')::DATE
FROM
months
WHERE
start_date < timestamp '2017-12-01' /*change this date to adjust range*/
),
subscription_months(start_date, end_date, user_id) as(
select
months.start_date::DATE,
months.end_date,
initial_subscription.user_id
from
subscription initial_subscription
left join lateral (
select
cancellation.timestamp
from
subscription cancellation
where
cancellation.user_id = initial_subscription.user_id
and cancellation.timestamp >= initial_subscription.timestamp
and cancellation.action = 'unsubscribed'
order by
cancellation.timestamp asc
limit 1
) as cancellation on true
inner join months on
initial_subscription.timestamp <= months.end_date
and (
cancellation.timestamp is null
or cancellation.timestamp >= months.end_date
)
where
initial_subscription.action = 'subscribed'
)
select
start_date,
end_date,
count(distinct user_id)
from
subscription_months
group by
start_date,
end_date
order by
start_date
【讨论】:
不错!这给出了预期的确切结果。我已经对我的 Postgres 版本的数据进行了尝试,它看起来非常准确。不过,我还没有检查所有的值。非常感谢!有什么想法可以避免LATERAL
加入吗?我没有看到任何在 Redshift 中复制它的方法。
这是另一个没有横向连接的版本。它使用另一个 CTE 来避免它。 sqlfiddle.com/#!17/fe3c7/59你怎么看?【参考方案3】:
这似乎需要一堆可能需要很长时间才能收敛的连接,具体取决于您的表大小。如果空间不是问题并且这些类型的查询很频繁,我将添加第三列,其中带有(二进制)标志,显示您可以过滤的最新操作。我的尝试:SQL Fiddle
-- get starting month
WITH start_month AS(
SELECT MIN(CAST(DATE_TRUNC('month', ts) AS DATE)) AS earliest
FROM test
),
-- bucket each date into months
month_buckets AS(
SELECT CAST(DATE_TRUNC('month', ts) AS DATE) AS month_bucket
FROM test
GROUP BY 1
),
-- for each month bucket, find all actions taken by each user upto that month
master AS (SELECT mb.month_bucket, user_id, actions, ts
FROM month_buckets mb
LEFT JOIN test
ON CAST(DATE_TRUNC('month', test.ts) AS DATE) <= mb.month_bucket
)
-- for each user, get the latest action and timestamp
-- group by month_bucket, count
SELECT m1.month_bucket AS month,
COUNT(m1.user_id) AS subscribers
FROM master m1
JOIN (SELECT month_bucket, user_id, MAX(ts) AS ts
FROM master
GROUP BY 1, 2
) m2
ON m1.month_bucket = m2.month_bucket
AND m1.user_id = m2.user_id
AND m1.ts = m2.ts
AND m1.actions = 'subscribed'
GROUP BY 1
ORDER BY 1;
【讨论】:
这似乎运作良好。非常感谢你的帮助!我将根据我的实际数据对此进行验证,看看是否发现任何差异。【参考方案4】:订阅用户总数为:
select count(*)
from
(
select distinct id
from subscribers
group by id
having count(*) in (1, 3, 5...) -- here you can use a table function to return odd numbers
) a
一段时间内的订阅数:
select count(distinct a.id)
from
(
select distinct id
from subscribers
group by id
having count(*) in (1, 3, 5...) -- here you can use a table function to return odd numbers
) a join
subscribers s on a.id = s.id
where timestamp between @date1 and @date2
注意:我没有在 Redshift 或 Postgres 中尝试过
【讨论】:
以上是关于计算每月的通讯订阅者总数的主要内容,如果未能解决你的问题,请参考以下文章