SQL统计活跃客户(用户数据连续性分析)
场景说明
对于一般数据分析项目,关于客户活跃度(有效性)相关维度数据统计必不可少。根据重要指标的持续性情况反应客户的粘性,衡量用户忠诚度指导业务开展,我们通常会考虑客户数据的一个连续性情况。例如:1、当月用户使用APP的连续登录天数超过等于3天,定义为本月活跃用户;2、客户在某银行存款当年有200天及其以上连续存款余额大于1千元,定义为有效客户;
思路分析
- 从业务系统ETL设计角度,可以考虑对于此类场景设计对应的信息记录表(客户连续登录次数记录表,字段客户ID,连续登录次数),每天根据用户行为汇总,例如我们根据用户操作日志记录的信息,可以统计出当天的所有登录用户,然后与昨日信息比较(在昨日连续登录次数上+1,昨日汇总中没有则连续登录次数记作1),然后将今日所有用户数据记录到实体表中。
- 从业务数据分析统计角度,即当前业务表中没有相关直接记录汇总信息表,只能通过现有的业务数据表,或日志表中数据进行分析找到定义的客户用户范围。就比如通过操作日志表,统计当月所有连续登录天数超过3天的用户:
- 第一步,汇总基础数据,把日志表中每天数据汇总到用户维度,找出每天有登录相关操作的用户,日志结果集就是字段就是客户ID+年月日就行;
- 第二步,分析汇总数据, 直接思路①:我要分析当天用户是否连续3天,那我把当T,T-1,T-2三天数据都拿出来看看都有的用户就是3天连续登录的,SQL角度相当于我要最低是从日志结果集至少取3次数据进行关联,但是如果规则改为连续4天,就要再加代码一共取4次灵活性很差不易拓展。分析思路②:取当天日期T数据然后和日期小于等于T,但是日期大于T-3的数据进行笛卡尔关联,我们再看如果统计用户不同日期出现次数等于3说明就是连续3天了,这种方式是取两个结果集关联,如果改为其它连续次数只需要改下限制条件中日期范围和判断次数的比较值即可;分析思路③:我们直接把用户+日期,按照日期顺序加上个递增序号,那么通过这个递增序号考虑,假如这个用户连续三天登录了,日期T的序号-2对应那条数据用户对应日期就应该是T-2,否则就不是连续3天了因为序号每天递增和日期递增量是保持一致的。
SQL示例(pg数据库样例)
用户日志明细表
-- 这里我们只记录登录用户的行为日志
CREATE TABLE DWPDATA.cust_log_det
(
cust_id VARCHAR(200) -- 用户ID
,happen_time TIMESTAMP(0) -- 发生时间
,action_desc TEXT -- 行为描述
)
;
-- 插入测试数据
INSERT INTO DWPDATA.cust_log_det
(
cust_id -- 用户ID
,happen_time -- 发生时间
,action_desc -- 行为描述
)
VALUES
('James23','2022-02-01 02:00:00'::TIMESTAMP(0),'登录')
,('James23','2022-02-01 10:00:00'::TIMESTAMP(0),'下载')
,('James23','2022-02-03 03:00:00'::TIMESTAMP(0),'登录')
,('James23','2022-02-03 06:00:00'::TIMESTAMP(0),'上传')
,('James23','2022-02-04 03:00:00'::TIMESTAMP(0),'登录')
,('James23','2022-02-05 06:00:00'::TIMESTAMP(0),'登录')
,('James06','2022-02-06 03:00:00'::TIMESTAMP(0),'登录')
,('James06','2022-02-07 06:00:00'::TIMESTAMP(0),'登录')
,('James06','2022-02-08 06:00:00'::TIMESTAMP(0),'登录')
,('Paul03','2022-02-06 03:00:00'::TIMESTAMP(0),'登录')
,('Paul03','2022-02-07 06:00:00'::TIMESTAMP(0),'登录')
,('Paul03','2022-02-10 06:00:00'::TIMESTAMP(0),'登录')
;
汇总日志明细到客户日期
我们按照客户加日期年月日方式把日志明细汇总聚合,得到了用户登录日期情况表,可以看到用户哪一天登录,一个用户一个日期一条数据。
CREATE TABLE DWPDATA.cust_login_date
(
cust_id VARCHAR(200) -- 用户ID
,login_date DATE -- 登录日期
)
;
INSERT INTO DWPDATA.cust_login_date
(
cust_id -- 用户ID
,login_date -- 登录日期
)
SELECT
cust_id -- 用户ID
,happen_time::DATE login_date -- 登录日期
FROM DWPDATA.cust_log_det
WHERE happen_time >= '2022-02-01 00:00:00' -- 根据需要限定日期范围 计算口径差异性不同限制数据范围不同 比如有的定义连续必须是本月内,那么上月后两天有登录当月1号有记录也不会算作活跃
AND happen_time < '2022-03-01 00:00:00'
GROUP BY 1,2
;
SELECT *
FROM DWPDATA.cust_login_date
ORDER BY 1,2
;
-- 查询结果
cust_id|login_date|
-------+----------+
James06|2022-02-06|
James06|2022-02-07|
James06|2022-02-08|
James23|2022-02-01|
James23|2022-02-03|
James23|2022-02-04|
James23|2022-02-05|
Paul03 |2022-02-06|
Paul03 |2022-02-07|
Paul03 |2022-02-10|
SQL查询活跃用户
直接SQL①:
要分析当天用户是否连续3天,那我把当T,T-1,T-2三天数据都拿出来看看都有的用户就是3天连续登录的,SQL角度相当于我要最低是从日志结果集至少取3次数据进行关联,但是如果规则改为连续4天,就要再加代码一共取4次灵活性很差不易拓展。
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
FROM DWPDATA.cust_login_date T01
LEFT JOIN DWPDATA.cust_login_date T02
ON T01.cust_id = T02.cust_id
AND T01.login_date = T02.login_date + 1
LEFT JOIN DWPDATA.cust_login_date T03
ON T01.cust_id = T03.cust_id
AND T01.login_date = T03.login_date + 2
WHERE T02.cust_id IS NOT NULL
AND T03.cust_id IS NOT NULL
ORDER BY 1,2
;
-- 查询结果
cust_id|login_date|
-------+----------+
James06|2022-02-08|
James23|2022-02-05|
分析SQL②:
取当天日期T数据然后和日期小于等于T,但是日期大于T-3的数据进行笛卡尔关联,我们再看如果统计用户不同日期出现次数等于3说明就是连续3天了,这种方式是取两个结果集关联,如果改为其它连续次数只需要改下限制条件中日期范围和判断次数的比较值即可;
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
,COUNT(T02.login_date) -- 近三天登录次数
,MAX(T02.login_date) -- 近三天登录过最大日期
,MIN(T02.login_date) -- 近三天登录过最小日期
FROM DWPDATA.cust_login_date T01
LEFT JOIN DWPDATA.cust_login_date T02
ON T01.cust_id = T02.cust_id
AND T01.login_date - 2 <= T02.login_date -- 限定比较范围为近3天
AND T01.login_date >= T02.login_date
GROUP BY 1,2
HAVING COUNT(T02.login_date) = 3 -- 近三天登录次数 为3 说明连续登录3天了
;
-- 查询结果
cust_id|login_date|count|max |min |
-------+----------+-----+----------+----------+
James23|2022-02-05| 3|2022-02-05|2022-02-03|
James06|2022-02-08| 3|2022-02-08|2022-02-06|
分析SQL③:
我们直接把用户+日期,按照日期顺序加上个递增序号,那么通过这个递增序号考虑,假如这个用户连续三天登录了,日期T的序号-2对应那条数据用户对应日期就应该是T-2,否则就不是连续3天了因为序号每天递增和日期递增量是保持一致的。
-- 使用 with 相当于建一个临时表,把排序后存储一下,然后可以自身关联,不用取原表两次
WITH VT_CUST_RK_INFO AS
(
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
,ROW_NUMBER()OVER(PARTITION BY T01.cust_id ORDER BY T01.login_date) rk -- 使用开窗函数,每个客户按照登录日期排序,加上序号
FROM DWPDATA.cust_login_date T01
)
SELECT
P01.cust_id -- 用户ID
,P01.login_date -- 登录日期
FROM VT_CUST_RK_INFO P01
LEFT JOIN VT_CUST_RK_INFO P02
ON P01.cust_id = P02.cust_id
AND P01.rk - 2 = P02.rk
WHERE P01.login_date - 2 = P02.login_date -- 序号和日期的差值一样证明此段是连续的
;
-- 查询结果
cust_id|login_date|
-------+----------+
James06|2022-02-08|
James23|2022-02-05|
分析SQL④:
在分析SQL③利用排序之后序号与日期的相同差值的解决方法后,思考可以直接利用函数LAG、LEAD。这两个函数,允许我们从窗口分区中,根据给定的相对于当前行的前偏移量( LAG )或后偏移量( LEAD ),并返回对应行的值
-
取当前数据向前偏移2行日期SQL,从新增列 lag_date 既是当前行按照顺序向前找2天对应的期
SELECT T01.cust_id -- 用户ID ,T01.login_date -- 登录日期 ,LAG(T01.login_date,2,NULL)OVER(PARTITION BY T01.cust_id ORDER BY T01.login_date) lag_date -- 当前向前偏移2行对应日期,获取不到给NULL FROM DWPDATA.cust_login_date T01 ORDER BY 1,2 -- 查询结果 cust_id|login_date|lag_date | -------+----------+----------+ James06|2022-02-06| | James06|2022-02-07| | James06|2022-02-08|2022-02-06| James23|2022-02-01| | James23|2022-02-03| | James23|2022-02-04|2022-02-01| James23|2022-02-05|2022-02-03| Paul03 |2022-02-06| | Paul03 |2022-02-07| | Paul03 |2022-02-10|2022-02-06| -
如果当前日期与lag_date差值为2说明,日期也是连续的了,所以可以用如下SQL找到活跃用户
SELECT
P01.cust_id -- 用户ID
,P01.login_date -- 登录日期
FROM
(
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
,LAG(T01.login_date,2,NULL)OVER(PARTITION BY T01.cust_id ORDER BY T01.login_date) lag_date -- 当前向前偏移2行对应日期
FROM DWPDATA.cust_login_date T01
) P01
WHERE P01.login_date - 2 = P01.lag_date
;
-- 查询结果
cust_id|login_date|
-------+----------+
James06|2022-02-08|
James23|2022-02-05|
总结分析SQL
此类问题核心是思考如何判断连续性,首选将数据转换处理汇总到对应需要判断连续性的维度上(保证一个日期一个客户一条数据),然后通过窗口函数方式直接增加一个有顺序的列,然后通过有顺序增加或者差异规律,去和日期的变化比较,二者一致则证明日期也是连续变化了。所以我们观察到这个规律,其实我们还可以进一步整理SQL,得到这个日期范围内客户连续登录情况比如如下第一段SQL,可以发现如果用户是连续登录的那么登录日期减去序号得到的日期就是开始连续的前天,也就是说获取登录日期减去序号如果是一样证明是在一个连续日期内,比如如下第2段SQL就可以反馈出用户连续登录情况!
SELECT
P01.cust_id -- 用户ID
,P01.login_date -- 登录日期
,P01.rk
,P01.login_date - P01.rk::INT "登录日期减去排序"-- 登录日期减去排序 可以理解我们获取如果是连续情况的开始日期
FROM
(
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
,ROW_NUMBER()OVER(PARTITION BY T01.cust_id ORDER BY T01.login_date) rk -- 使用开窗函数,每个客户按照登录日期排序,加上序号
FROM DWPDATA.cust_login_date T01
) P01
ORDER BY 1,2
-- 查询结果
cust_id|login_date|rk|登录日期减去排序 |
-------+----------+--+----------+
James06|2022-02-06| 1|2022-02-05|
James06|2022-02-07| 2|2022-02-05|
James06|2022-02-08| 3|2022-02-05|
James23|2022-02-01| 1|2022-01-31|
James23|2022-02-03| 2|2022-02-01|
James23|2022-02-04| 3|2022-02-01|
James23|2022-02-05| 4|2022-02-01|
Paul03 |2022-02-06| 1|2022-02-05|
Paul03 |2022-02-07| 2|2022-02-05|
Paul03 |2022-02-10| 3|2022-02-07|
SELECT
P01.cust_id cust_id -- 用户ID
,P01.login_date - P01.rk::INT ld_login_date -- 登录日期减去排序
,MIN(P01.login_date) begin_login_date -- 登录起日期
,MAX(P01.login_date) end_login_date -- 登录止日期
,COUNT(1) login_cc -- 登录次数
FROM
(
SELECT
T01.cust_id -- 用户ID
,T01.login_date -- 登录日期
,ROW_NUMBER()OVER(PARTITION BY T01.cust_id ORDER BY T01.login_date) rk -- 使用开窗函数,每个客户按照登录日期排序,加上序号
FROM DWPDATA.cust_login_date T01
) P01
GROUP BY 1,2
ORDER BY 1,5 DESC
-- 获取的结果就是所有用户连续登录情况反馈
-- 用户 James06 连续登录过3天,分别是 2022-02-06 2022-02-07 2022-02-08 三天
-- 因此我们在这个结果集上限定 连续登录大于等于 3 的就把所有用户信息,同时如果是想找到哪些天是的话,可以关联表汇总日志明细到客户日期,限定对应客户的登录日期在 [begin_login_date, end_login_date]范围即可,具体SQL这里就不写了
cust_id|ld_login_date|begin_login_date|end_login_date|login_cc|
-------+-------------+----------------+--------------+--------+
James06| 2022-02-05| 2022-02-06| 2022-02-08| 3|
James23| 2022-02-01| 2022-02-03| 2022-02-05| 3|
James23| 2022-01-31| 2022-02-01| 2022-02-01| 1|
Paul03 | 2022-02-05| 2022-02-06| 2022-02-07| 2|
Paul03 | 2022-02-07| 2022-02-10| 2022-02-10| 1|
SQL策略:基于日志分析用户活跃度连续性
本文介绍了如何使用SQL查询在业务系统中分析用户活跃度,如连续登录天数和存款连续性,通过ETL设计和不同的SQL技巧,如窗口函数和日期差值,实现高效的数据统计和连续性判断。
3915

被折叠的 条评论
为什么被折叠?



