如何根据事件在 SQL 中计算客户保留率?

Posted

技术标签:

【中文标题】如何根据事件在 SQL 中计算客户保留率?【英文标题】:How to calculate customer retention in SQL based on events? 【发布时间】:2020-08-25 13:56:56 【问题描述】:

我正在尝试创建一个 SQL 语句来找出哪些客户没有连续参加三个活动

表 1 - 客户: 客户 ID、客户名称

+-------------+---------------+
| Customer ID | Customer Name |
+-------------+---------------+
|          01 | Customer 01   |
|          02 | Customer 02   |
|          03 | Customer 03   |
+-------------+---------------+

表 2 - 事件 事件 ID、事件日期、事件名称

+----------------------------------+
| Event ID  Event Date  Event Name |
+----------------------------------+
| 01        01/01/2020  Event 01   |
| 02        01/15/2020  Event 02   |
| 03        02/15/2020  Event 03   |
| 04        03/13/2020  Event 04   |
| 05        05/17/2020  Event 05   |
| 06        06/20/2020  Event 06   |
+----------------------------------+

表 3 - 事件活动 事件 ID、客户 ID

+----------+-------------+----+
| Event ID | Customer ID |    |
+----------+-------------+----+
|       01 |             | 01 |
|       01 |             | 02 |
|       01 |             | 03 |
|       02 |             | 01 |
|       03 |             | 01 |
|       03 |             | 02 |
|       04 |             | 01 |
|       05 |             | 01 |
|       06 |             | 01 |
|       06 |             | 03 |
+----------+-------------+----+

现在我正在寻找那些连续没有参加 3 场活动的客户。

所以在给定的示例中,客户 2 和客户 3。

我使用了史蒂夫的建议。这里是更新的 SQL 语句:

drop table if exists dbo.customer;
create table dbo.customer(
  CustID        int not null,
  CustName      varchar(20) not null);
insert dbo.customer(CustID, CustName) values
(1,'Cust 1'),
(2,'Cust 2'),
(3,'Cust 3'),
(4,'Cust 4'),
(5,'Cust 5')
;


drop table if exists dbo.events;
create table dbo.events(
  EventID       int not null,
  EventDate     date not null,
  EventName     varchar(20) not null);
insert dbo.events(EventId, EventDate, EventName) values
(1,'2020-01-01','Event 1'),
(2,'2020-01-15','Event 2'),
(3,'2020-02-15','Event 3'),
(4,'2020-03-13','Event 4'),
(5,'2020-05-17','Event 5'),
(6,'2020-06-20','Event 6');


drop table if exists dbo.eventactivity;
create table dbo.eventactivity(
  EventID       int not null,
  CustID        int not null);
insert dbo.eventactivity(EventID, CustID) values
(1,1),
(1,2),
(1,3),
(1,4),
(1,5),
(2,1),
(2,2),
(2,4),
(2,5),
(3,1),
(3,5),
(4,1),
(4,5),
(5,1),
(5,2),
(5,3),
(5,5),
(6,1),
(6,2),
(6,3);
(6,5);

这里:

;with
events_sorted as (
    select e.*, row_number() over (order by EventDate) seq from dbo.events e),
activity_lag as 
(
    select
      a.*, e.seq,
      lag(e.seq, 1, 0) over (partition by CustId order by e.seq) lag_seq,
      iif(lag(e.seq, 1, 0) over (partition by CustId order by e.seq)=0, 1, 
          iif((e.seq-lag(e.seq, 1, 0) over (partition by CustId order by e.seq))>1, 1, 0)) seq_break
    from dbo.eventactivity a
         join events_sorted e on a.EventID=e.EventID
),
activity_lag_sum as (
    select
      alag.*, sum(seq_break) over (partition by CustId order by alag.seq) seq_grp
    from
      activity_lag alag
),
three_in_a_row_cte as (
    select distinct CustId
    from activity_lag_sum
    group by CustID, seq_grp
    having count(*)>=3
    )
    select * 
from customer c
where not exists(select 1
                 from three_in_a_row_cte r
                 where c.CustID=r.CustID);

问题是,这会返回客户 2、客户 3、客户 4 - 客户 2 确实参加了 2 个活动,跳过了 2 个,参加了 2 个,所以客户 2 不应该在列表中。

有什么建议吗?

【问题讨论】:

您使用的是哪种 DBMS 产品? “SQL”只是所有关系数据库都使用的一种查询语言,而不是特定数据库产品的名称。请为您使用的数据库产品添加tag。 Why should I tag my DBMS 所以您正在寻找没有连续参加过三场活动的客户?或者您是在寻找连续三场活动中至少失踪一次的客户? 您好 Jere,第一部分 - “连续未参加过 3 场活动的客户” - 或者换句话说 - 未连续参加 3 场活动的客户。 你试过什么了吗??? 【参考方案1】:

以下查询返回的 CustId 具有:1) 跳过 3 个或更多活动,或 2) 参加的活动总数少于 3 个。

;with
events_sorted as (
    select e.*, row_number() over (order by EventDate) seq from #events e),
activity_lag as 
(
    select
      a.*, e.seq,
      lag(e.seq, 1, 0) over (partition by CustId order by e.seq) lag_seq,
      iif(lag(e.seq, 1, 0) over (partition by CustId order by e.seq)=0, 1, 
          iif((e.seq-lag(e.seq, 1, 0) over (partition by CustId order by e.seq))>1, 1, 0)) seq_break
    from #eventactivity a
         join events_sorted e on a.EventID=e.EventID
)
select distinct CustId
from activity_lag
where seq-lag_seq>3
union all
select CustId
from activity_lag
group by CustId
having count(*)<3;

结果

CustId
3
4

【讨论】:

太棒了(如果我能用更大的字母写的话)...非常感谢这里的帮助。 如果还有什么问题请告诉我【参考方案2】:

您只需要连续跳过 3 个或更多事件的客户,您可以通过从事件活动表本身查询来获得。请在下面找到查询和查询结果:-

创建表格

     create table event_activity ("event_id" varchar(2),"customer_id" varchar(2))
     insert into event_activity
     values ('01','01'),('01','02'),('01','03'),('02','01'),('02','03'),('03','01'),
     ('03','02'),('04','01'),('04','02'),('05','02'),('06','01'),('06','03'), 
     ('07','03'),('08','04'),('12','04'),('13','05')

以上查询将产生下表:-

  event_id | customerid 
  ---------------------    
      01   |   01
      01   |   02
      01   |   03
      02   |   01
      02   |   03
      03   |   01
      03   |   02
      04   |   01
      04   |   02
      05   |   02
      06   |   01
      06   |   03
      07   |   03
      08   |   04
      12   |   04
      13   |   05
      

从上表我们可以观察到除了客户 4 和 5 之外的所有客户连续跳过少于 3 个事件。根据您的问题,我们只需要 4 和 5,因为 4 已连续跳过 3 场活动,但 5 只参加了 1 场活动。

PS : - 在这里您可以发现客户 3 也跳过了 3 个事件,但在此之前他有 参加了一些活动没有跳过任何所以,它必须被淘汰。

最终查询

    select c.customer_id
    from
    (
       select customer_id, 
              skipped_count, 
              lag(skipped_count,1) over (partition by customer_id order by event_id) 
              as ref
       from 
          ( 
             select customer_id, 
                    event_id,
                    LAG(event_id,1) over (partition by customer_id order by event_id) 
                    as previous_event,(event_id - LAG(event_id,1) over (partition by 
                    customer_id order by event_id)-1) as skipped_count
              from 
                 (
                   select CONVERT(int,event_id) as event_id, 
                          CONVERT(int, customer_id) as customer_id 
                          from event_activity
                 )a
            )b
     )c
     join
     (
         select convert(int,customer_id) as customer_id,
                count(event_id) as count_event
           from event_activity
           group by customer_id
     )d
     on c.customer_id=d.customer_id
     where (skipped_count >=3 and ref is null)
     or count_event = 1
     or (skipped_count >=3 and ref > 2)

输出

    4
    5

【讨论】:

以上是关于如何根据事件在 SQL 中计算客户保留率?的主要内容,如果未能解决你的问题,请参考以下文章

根据 SQL 中的日期计算百分位数

如何用sql语句将计算结果保留两位小数

SQL如何计算订单购买之间的平均时间? (根据下一行和上一行进行sql计算)

SQL sever 2008 如何根据一个表中的两个时间计算出时间差

如何在 SQL Bigquery 中的另一个事件之前计算特定事件的数量?

如何在 SQL 中按时间顺序计算事件?