Bootstrap

SQL进阶技巧:如何分析连续签到领金币数问题?

目录

0  题目需求

1 数据准备

2 问题分析

2.1 代码实现

2.2 代码功能分析

第一段 SQL

第二段 SQL

第三段 SQL

3 分析与小结 

核心技巧总结

3.1. 窗口函数

3.2. 日期处理

3. 3 DATE_SUB 函数与连续数据的判断 

3.4. 条件求和

3.4 总结

往期精彩


0  题目需求

用户每天签到可以领1金币,并可以累计签到天数,连续签到的第3、7天分别可以额外领2和6金币。

每连续签到7天重新累积签到天数。

从用户登录明细表中求出每个用户金币总数,并按照金币总数倒序排序

结果如下:

User_id(用户id)

Sum_coin_cn(金币总数)

101

7

109

3

107

3

102

3

106

2

104

2

103

2

1010

2

108

1

105

1

1 数据准备

1)表结构

user_id(用户id)

ip_address(ip地址)

login_ts(登录时间)

logout_ts(登出时间)

101

180.149.130.161

2021-09-21 08:00:00

2021-09-27 08:30:00

102

120.245.11.2

2021-09-22 09:00:00

2021-09-27 09:30:00

103

27.184.97.3

2021-09-23 10:00:00

2021-09-27 10:30:00

2)建表语句

DROP TABLE IF EXISTS user_login_detail;

CREATE TABLE user_login_detail

(

`user_id` string comment '用户id',

`ip_address` string comment 'ip地址',

`login_ts` string comment '登录时间',

`logout_ts` string comment '登出时间'

) COMMENT '用户登录明细表'

ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';

3)数据装载

INSERT overwrite table user_login_detail

VALUES ('101', '180.149.130.161', '2021-09-21 08:00:00', '2021-09-27 08:30:00'),

       ('101', '180.149.130.161', '2021-09-27 08:00:00', '2021-09-27 08:30:00'),

       ('101', '180.149.130.161', '2021-09-28 09:00:00', '2021-09-28 09:10:00'),

       ('101', '180.149.130.161', '2021-09-29 13:30:00', '2021-09-29 13:50:00'),

       ('101', '180.149.130.161', '2021-09-30 20:00:00', '2021-09-30 20:10:00'),

       ('102', '120.245.11.2', '2021-09-22 09:00:00', '2021-09-27 09:30:00'),

       ('102', '120.245.11.2', '2021-10-01 08:00:00', '2021-10-01 08:30:00'),

       ('102', '180.149.130.174', '2021-10-01 07:50:00', '2021-10-01 08:20:00'),

       ('102', '120.245.11.2', '2021-10-02 08:00:00', '2021-10-02 08:30:00'),

       ('103', '27.184.97.3', '2021-09-23 10:00:00', '2021-09-27 10:30:00'),

       ('103', '27.184.97.3', '2021-10-03 07:50:00', '2021-10-03 09:20:00'),

       ('104', '27.184.97.34', '2021-09-24 11:00:00', '2021-09-27 11:30:00'),

       ('104', '27.184.97.34', '2021-10-03 07:50:00', '2021-10-03 08:20:00'),

       ('104', '27.184.97.34', '2021-10-03 08:50:00', '2021-10-03 10:20:00'),

       ('104', '120.245.11.89', '2021-10-03 08:40:00', '2021-10-03 10:30:00'),

       ('105', '119.180.192.212', '2021-10-04 09:10:00', '2021-10-04 09:30:00'),

       ('106', '119.180.192.66', '2021-10-04 08:40:00', '2021-10-04 10:30:00'),

       ('106', '119.180.192.66', '2021-10-05 21:50:00', '2021-10-05 22:40:00'),

       ('107', '219.134.104.7', '2021-09-25 12:00:00', '2021-09-27 12:30:00'),

       ('107', '219.134.104.7', '2021-10-05 22:00:00', '2021-10-05 23:00:00'),

       ('107', '219.134.104.7', '2021-10-06 09:10:00', '2021-10-06 10:20:00'),

       ('107', '27.184.97.46', '2021-10-06 09:00:00', '2021-10-06 10:00:00'),

       ('108', '101.227.131.22', '2021-10-06 09:00:00', '2021-10-06 10:00:00'),

       ('108', '101.227.131.22', '2021-10-06 22:00:00', '2021-10-06 23:00:00'),

       ('109', '101.227.131.29', '2021-09-26 13:00:00', '2021-09-27 13:30:00'),

       ('109', '101.227.131.29', '2021-10-06 08:50:00', '2021-10-06 10:20:00'),

       ('109', '101.227.131.29', '2021-10-08 09:00:00', '2021-10-08 09:10:00'),

       ('1010', '119.180.192.10', '2021-09-27 14:00:00', '2021-09-27 14:30:00'),

       ('1010', '119.180.192.10', '2021-10-09 08:50:00', '2021-10-09 10:20:00');

2 问题分析

2.1 代码实现

-- 求连续并标志是连续的第几天
select
  t1.user_id,
  t1.login_date,
  date_sub(t1.login_date,t1.rk) login_date_rk,
  count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
from
  (
   select
     user_id,
     date_format(login_ts,'yyyy-MM-dd') login_date,
     rank()over(partition by user_id order by date_format(login_ts,'yyyy-MM-dd')) rk
   from
     user_login_detail
   group by
     user_id,date_format(login_ts,'yyyy-MM-dd')
)t1

--求出金币数量,以及签到奖励的金币数量
select
  t2.user_id,
  max(t2.counti_cn)+sum(if(t2.counti_cn%3=0,2,0))+sum(if(t2.counti_cn%7=0,6,0)) coin_cn
from
  (
select
  t1.user_id,
     t1.login_date,
     date_sub(t1.login_date,t1.rk) login_date_rk,
     count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
   from
     (
      select
        user_id,
        date_format(login_ts,'yyyy-MM-dd') login_date,
        rank()over(partition by user_id order by date_format(login_ts,'yyyy-MM-dd')) rk
      from
        user_login_detail
      group by
        user_id,date_format(login_ts,'yyyy-MM-dd')
   )t1
)t2
group by
  t2.user_id,t2.login_date_rk

-- 求出每个用户的金币总数

select
  t3.user_id,
  sum(t3.coin_cn) sum_coin_cn
from 
  (
    select
      t2.user_id,
      max(t2.counti_cn)+sum(if(t2.counti_cn%3=0,2,0))+sum(if(t2.counti_cn%7=0,6,0)) coin_cn
    from
      (
    select
      t1.user_id,
         t1.login_date,
         date_sub(t1.login_date,t1.rk) login_date_rk,
         count(*)over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) counti_cn
       from
         (
          select
            user_id,
            date_format(login_ts,'yyyy-MM-dd') login_date,
            rank()over(partition by user_id order by date_format(login_ts,'yyyy-MM-dd')) rk
          from
            user_login_detail
          group by
            user_id,date_format(login_ts,'yyyy-MM-dd')
       )t1
    )t2
    group by
      t2.user_id,t2.login_date_rk
    )t3
group by
  t3.user_id
order by
  sum_coin_cn desc

2.2 代码功能分析

第一段 SQL
  • 功能
    • 找出用户连续登录的信息,包括用户 ID、登录日期、经过调整的登录日期(通过 date_sub(t1.login_date,t1.rk))以及连续登录的天数。
  • 实现方式
    1. 首先在子查询 t1 中:
      • 使用 date_format(login_ts,'yyyy-MM-dd') 将 login_ts 转换为 yyyy-MM-dd 格式的日期,命名为 login_date
      • 使用 rank() 窗口函数,按照 user_id 分区,按照 login_date 排序,为每个用户的登录日期分配一个排名 rk
      • 对 user_id 和 login_date 进行分组,以确保数据唯一。
    2. 主查询中:
      • 使用 date_sub(t1.login_date,t1.rk) 计算出一个新的日期 login_date_rk,用于判断用户登录是否连续。
      • 使用 count(*) over(partition by t1.user_id, date_sub(t1.login_date,t1.rk) order by t1.login_date) 窗口函数,按照 user_id 和 login_date_rk 分区,按照 login_date 排序,计算连续登录的天数 counti_cn
第二段 SQL
  • 功能
    • 计算用户可以获得的金币数量,根据连续登录天数计算签到奖励的金币数量。
  • 实现方式
    1. 将第一段 SQL 的查询结果作为子查询 t2
    2. 主查询中:
      • 使用 max(t2.counti_cn) 取每个用户的最大连续登录天数。
      • 使用 sum(if(t2.counti_cn%3=0,2,0)) 当连续登录天数是 3 的倍数时,给予 2 个金币奖励。
      • 使用 sum(if(t2.counti_cn%7=0,6,0)) 当连续登录天数是 7 的倍数时,给予 6 个金币奖励。
      • 计算 coin_cn 作为用户的金币数量。
第三段 SQL
  • 功能
    • 计算每个用户的金币总数。
  • 实现方式
    1. 将第二段 SQL 的查询结果作为子查询 t3
    2. 主查询中:
      • 使用 sum(t3.coin_cn) 计算每个用户的金币总数 sum_coin_cn
      • 按照 user_id 分组并按照 sum_coin_cn 降序排序。

 

  • 整个 SQL 代码通过窗口函数和日期计算,将用户登录数据进行了复杂的分析和处理。
    • 首先使用 RANK() 函数对用户的登录日期进行排序和排名,为后续计算连续登录提供基础。
    • 通过 DATE_SUB() 函数结合排名,找出连续登录的序列,并计算连续登录天数。
    • 然后根据连续登录天数的规则,使用 IF() 函数和 SUM() 函数添加相应的金币奖励。
    • 最后对用户的金币数进行求和并排序,以展示用户的金币总数排名。

3 分析与小结 

核心技巧总结

3.1. 窗口函数

  • RANK () 函数
    • 在 login_rank 子查询中使用 RANK() OVER (PARTITION BY user_id ORDER BY DATE_FORMAT(login_ts,'yyyy-MM-dd')) AS rk
    • 核心功能:为每个用户的登录日期生成排名,按用户 ID 进行分区,按登录日期进行排序。这有助于将用户的登录记录按照时间顺序编号,为后续判断登录是否连续提供依据。
    • 重要性:窗口函数是 SQL 中的高级功能,能方便地进行数据分组内的排序和排名,在处理复杂的分析需求时非常有用,比如对用户的连续操作进行排名,以便后续计算连续操作的天数或次数。

3.2. 日期处理

  • DATE_FORMAT 函数
    • 在 login_rank 子查询中使用 DATE_FORMAT(login_ts,'yyyy-MM-dd') AS login_date
    • 核心功能:将日期时间类型的数据(如 login_ts)转换为特定的日期格式(如 yyyy-MM-dd),以便进行按天的分组和排序操作。
    • 重要性:在处理时间序列数据时,将日期时间精确到所需的粒度,方便进行时间范围的筛选、分组和比较,确保日期比较的一致性和准确性。

3. 3 DATE_SUB 函数与连续数据的判断 

  • 在子查询中使用 DATE_SUB(lr.login_date, lr.rk) AS login_date_rk
    • 核心功能:通过将日期减去排名,找出连续登录的日期序列。对于连续的登录记录,这个操作会产生相同的 login_date_rk 值,因为连续的日期减去递增的排名会得到一个相同的结果序列。
    • 重要性:这是一种判断连续数据的巧妙方法,可用于分析用户的连续行为(如连续登录、连续操作等),适用于多种业务场景,如用户行为分析、设备连续运行时间统计等。

3.4. 条件求和

  • SUM 和 IF 函数的组合
    • 在 coin_reward 子查询中使用 SUM(IF(cl.counti_cn % 3 = 0, 2, 0)) 和 SUM(IF(cl.counti_cn % 7 = 0, 6, 0))
    • 核心功能:根据条件对满足条件的数据进行求和,如根据连续登录天数是否为 3 或 7 的倍数添加不同的奖励金币。
    • 重要性:在需要根据数据的不同情况添加不同权重或奖励时,这种组合可以灵活地根据数据条件计算出所需的结果,是实现复杂业务逻辑计算的常用技巧,如根据不同销售额计算不同提成、根据不同的用户行为给予不同积分等。

3.4 总结

这些核心技巧相互配合,实现了从用户登录信息的分析到用户金币奖励的计算。通过使用窗口函数对用户登录记录进行排名,结合日期处理函数和 DATE_SUB 函数判断连续登录行为,使用条件求和根据不同的连续登录天数计算金币奖励,再通过分组和聚合函数计算用户的总金币数。这些技巧在不同的业务场景中可以灵活应用,通过调整函数的使用方式和条件判断,可以处理如用户行为分析、业务指标计算、用户激励机制等各种业务需求。

往期精彩

数势科技指标平台, 让数据产生最大价值

数据科学与SQL:如何利用SQL计算线性回归系数?

SQL进阶技巧:如何利用SQL巧解公务员逻辑推理题?【修正版】

SQL进阶技巧:如何实现分钟级的趋势图?

数据科学与SQL:组距分组分析 | 区间分布问题

SQL进阶技巧:非等值连接--单向近距离匹配

 

 

如果您觉得本文还不错,对你有帮助,那么不妨可以关注一下我的数字化建设实践之路专栏,这里的内容会更精彩。

专栏 原价99,现在活动价59.9,按照阶梯式增长,还差5个人上升到69.9,最终恢复到原价

专栏优势
(1)一次收费持续更新。
(2)实战中总结的SQL技巧,帮助SQLBOY 在SQL语言上有质的飞越,无论你应对业务难题及面试都会游刃有余【全网唯一讲SQL实战技巧,方法独特】

(3)实战中数仓建模技巧总结,让你认识不一样的数仓。【数据建模+业务建模,不一样的认知体系】(如果只懂数据建模而不懂业务建模,数仓体系认知是不全面的)

(4)数字化建设当中遇到难题解决思路及问题思考。

 

我的专栏具体链接如下

数字化建设通关指南_莫叫石榴姐的博客-CSDN博客

https://blog.csdn.net/godlovedaniel/category_12706766.html

;