SQL 统计用户留存

问题描述

有一个用来记录每日客户消耗数据的表 t,它的表结构以下:mysql

字段 类型 描述
created_day Date 消耗日期
customer_id Integer 客户ID
amount Integer 消耗金额

要求:sql

统计出头部客户、腰部客户、尾部客户在上个月(2020-06-01 ~ 2020-06-30)的留存状况。微信

输出结果的格式:.net

层级 客户数量 留存数量
头部客户
腰部客户
尾部客户

数据定义:code

  • 头部客户:上个月消耗金额大于等于 30000 的客户;
  • 腰部客户:上个月消耗金额在 10000 ~ 30000(不包含 30000) 的客户;
  • 尾部客户:上个月消耗金额小于 10000 的客户;
  • 留存:最近两个月(上个月和本月)消耗金额大于 0 的客户;
  • 时间:上个月(2020-06-01 ~ 2020-06-30)、本月(2020-07-01 ~ 2020-07-31)

解决方案

今天这个问题和我上一篇文章里面的问题很类似,只不过这里要求多统计一列,所以,解决的思路也差很少。get

下面我将用 CTE 来演示每一个步骤。it

第一步,计算出上个月每一个客户的消耗金额。编译

with t1 AS 
(SELECT 
  customer_id,
  SUM(amount) AS amount 
FROM
  t 
WHERE created_day BETWEEN '2020-06-01' 
  AND '2020-06-30' 
GROUP BY customer_id)
SELECT * FROM t1

第二步,在第一步的基础上,统计头部客户、腰部客户、尾部客户的数量。table

t2 AS 
(SELECT 
  CASE
    WHEN amount >= 30000 
    THEN 1 
    WHEN amount >= 10000 
    THEN 2 
    ELSE 3 
  END AS customer_level,
  COUNT(*) AS customter_cnt 
FROM
  t1 
GROUP BY customer_level) 
SELECT * FROM t2

在脚本中使用代码 一、二、3 分别表示头部客户、腰部客户、尾部客户。class

须要注意的是,在 GROUP BY 子句中使用了 SELECT 子句中的字段别名 customer_level,这种语法在其它数据中是编译不经过的。

第三步,计算留存。根据留存的定义,只要客户在本月中有消耗,就计入留存数。好比客户 A,A 上个月的消耗金额是 40000,那么 A 就是头部客户,假如 A 在本月的消耗金额大于 0,A 就为【留存】贡献了 1 。

咱们在 t1 以后插入表达式 t12t12 的脚本以下:

t12 AS 
(SELECT 
  t1.customer_id,
  t1.amount,
  IF(tmp.amount > 0, 1, 0) AS keep_state 
FROM
  t1 
  LEFT JOIN 
    (SELECT 
      customer_id,
      SUM(amount) AS amount 
    FROM
      t 
    WHERE created_day BETWEEN '2020-07-01' 
      AND '2020-07-31' 
    GROUP BY customer_id) tmp 
    ON tmp.customer_id = t1.customer_id)
SELECT * FROM t12

t12 中的左链接也能够改形成标量子查询。

完整的 SQL 实现:

with t1 AS 
(SELECT 
  customer_id,
  SUM(amount) AS amount 
FROM
  t 
WHERE created_day BETWEEN '2020-06-01' 
  AND '2020-06-30' 
GROUP BY customer_id),
t12 AS 
(SELECT 
  t1.customer_id,
  t1.amount,
  IF(tmp.amount > 0, 1, 0) AS keep_state 
FROM
  t1 
  LEFT JOIN 
    (SELECT 
      customer_id,
      SUM(amount) AS amount 
    FROM
      t 
    WHERE created_day BETWEEN '2020-07-01' 
      AND '2020-07-31' 
    GROUP BY customer_id) tmp 
    ON tmp.customer_id = t1.customer_id),
t2 AS 
(SELECT 
  CASE
    WHEN amount >= 30000 
    THEN 1 
    WHEN amount >= 10000 
    THEN 2 
    ELSE 3 
  END AS customer_level,
  COUNT(*) AS customter_cnt,
  SUM(keep_state) AS keep_cnt 
FROM
  t12 
GROUP BY customer_level) 

SELECT 
  CASE
    customer_level 
    WHEN 1 
    THEN '头部客户' 
    WHEN 2 
    THEN '腰部客户' 
    ELSE '尾部客户' 
  END AS '层级',
  customter_cnt AS '客户数量',
  keep_cnt AS '留存数量' 
FROM
  t2 
ORDER BY customer_level

感兴趣的朋友能够尝试不使用左链接或者标量子查询的写法,而是只查一次 t 表就能实现需求。

本文分享自微信公众号 - SQL实现(gh_684ee9235a26)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索