Sql经典案例学习0.

1.求某一时刻最大人数

用户行为日志表tb_user_log

iduidartical_idin_timeout_timesign_cin
110190012021-11-01 10:00:002021-11-01 10:00:110
210290012021-11-01 10:00:092021-11-01 10:00:380
310390012021-11-01 10:00:282021-11-01 10:00:580
410490022021-11-01 11:00:452021-11-01 11:01:110
510590012021-11-01 10:00:512021-11-01 10:00:590
610690022021-11-01 11:00:552021-11-01 11:01:240
710790012021-11-01 10:00:01

2021-11-01 10:01:50

0

(uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到)

问题:统计每篇文章同一时刻最大在看人数,如果同一时刻有进入也有离开时,先记录用户数增加再记录减少,结果按最大人数降序。

输出示例

示例数据的输出结果如下

artical_idmax_uv
90013
90022

解法:

一直以来,都觉得这种球最大的同时在线/阅读人数很难,但是在参考大家的思路后,感觉很清晰,一下子就通透了。具体的步骤是:

①将用户的进入时间单独拎出来,同时记为1;离开时间单独拎出来,同时记为-1,这样就聚合这两个表,按照时间排序,意思就是:进去一个加1,离开一个减1。

②然后利用窗口函数对计数(1或者-1)求累计和,因为题目规定:同一时间有就有出的话先算进来的后算出去的,所以排序的时候就要看好了先按时间排序,再按计数排序!

③然后再在每个分组里面去求最大的累积和就是最多同时在线的人数了!

select b.artical_id,max(sub) max_uv
from (select a.artical_id,sum(a.num) over (partition by a.artical_id order by dt,a.num desc) as sub
from (
    select artical_id,in_time dt,1 num
    from tb_user_log
    where artical_id != 0
    union all 
    select artical_id,out_time dt,-1 num
    from tb_user_log
    where artical_id != 0
)  as a) as b
group by artical_id
order by max_uv desc

引申问题:

1.为什么不能直接在sum( ) over( ) 外面直接套一个max(sum( ) over( ))?

是因为分区(partition by​)不是分组(group by),分组会对 artical_id去重,而分区不会。分区后artical_id还是有很多行,不能直接用max()这个聚合函数

2.为什么用union all?

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL。

2.求次日用户的留存率

用户行为日志表tb_user_log

iduidartical_idin_timeout_timesign_cin
110102021-11-01 10:00:002021-11-01 10:00:421
210290012021-11-01 10:00:002021-11-01 10:00:090
310390012021-11-01 10:00:012021-11-01 10:01:500
410190022021-11-02 10:00:092021-11-02 10:00:280
510390022021-11-02 10:00:512021-11-02 10:00:590
610490012021-11-02 11:00:282021-11-02 11:01:240
710190032021-11-03 11:00:55

2021-11-03 11:01:24

0
810490032021-11-03 11:00:452021-11-03 11:00:550
910590032021-11-03 11:00:532021-11-03 11:00:590
1010190022021-11-04 11:00:552021-11-04 11:00:590

(uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到)

问题:统计2021年11月每天新用户的次日留存率(保留2位小数)

  • 次日留存率为当天新增的用户数中第二天又活跃了的用户数占比。
  • 如果in_time-进入时间out_time-离开时间跨天了,在两天里都记为该用户活跃过,结果按日期升序。

输出示例

示例数据的输出结果如下

dtuv_left_rate
2021-11-010.67
2021-11-021.00
2021-11-030.00

解法:

1.求出每个用户的首次活跃日期:

select uid,min(date(in_time)) dt 
from tb_user_log
group by uid

2.求出每个用户每次活跃的日期:

select uid,date(in_time) df 
from tb_user_log
where artical_id !=0
union
select uid,date(out_time) df 
from tb_user_log
where artical_id !=0

用union连接起去重作用,用户单日多次登录只计一次活跃

3.左连接二表,用第二张表日期-1=第一张表日期,用户名相同为连接条件,若新用户次日未活跃,则左连接未匹配到为null

(select uid,min(date(in_time)) dt 
from tb_user_log
group by uid) as a 
left join 
(select uid,date(in_time) df 
from tb_user_log
where artical_id !=0
union
select uid,date(out_time) df 
from tb_user_log
where artical_id !=0 ) as b
on a.uid=b.uid and a.dt=date_sub(b.df,interval 1 day)

我们便得到了一张新用户次日仍然留存的表,未留存的为null

4.在该表中进行查询,聚合即可

select a.dt,
       round(count(b.uid)/ count(a.uid),2) as uv_left_rate
from (select uid,min(date(in_time)) dt 
from tb_user_log
group by uid) as a 
left join 
(select uid,date(in_time) df 
from tb_user_log
where artical_id !=0
union
select uid,date(out_time) df 
from tb_user_log
where artical_id !=0 ) as b
on a.uid=b.uid and a.dt=date_sub(b.df,interval 1 day)
where date_format(a.dt,"%Y-%m")='2021-11'
group by a.dt

拓展:

DATE_SUB() 函数

DATE_SUB() 函数从日期减去指定的时间间隔。

DATE_SUB(date,INTERVAL expr type)

date 参数是合法的日期表达式。expr 参数是您希望添加的时间间隔。

本次使用日期减一天:date_sub(dt,interval 1 day)

                                    adddate(dt,interval -1 day)

type 参数可以是下列值:

Type 值
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
SECOND_MICROSECOND
MINUTE_MICROSECOND
MINUTE_SECOND
HOUR_MICROSECOND
HOUR_SECOND
HOUR_MINUTE
DAY_MICROSECOND
DAY_SECOND
DAY_MINUTE
DAY_HOUR
YEAR_MONTH

3.求连续签到积分问题

用户行为日志表tb_user_log

iduidartical_idin_timeout_timesign_in
110102021-07-07 10:00:002021-07-07 10:00:091
210102021-07-08 10:00:002021-07-08 10:00:091
310102021-07-09 10:00:002021-07-09 10:00:421
41010

2021-07-10 10:00:00

2021-07-10 10:00:091
510102021-07-11 23:59:552021-07-11 23:59:591
610102021-07-12 10:00:282021-07-12 10:00:501
710102021-07-13 10:00:28

2021-07-13 10:00:50

1
810202021-10-01 10:00:282021-10-01 10:00:501
910202021-10-02 10:00:012021-10-02 10:01:501
1010202021-10-03 10:00:552021-10-03 11:00:591
1110202021-10-04 10:00:452021-10-04 11:00:550
1210202021-10-05 10:00:532021-10-05 11:00:591
1310202021-10-06 10:00:452021-10-06 11:00:551

(uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到)

场景逻辑说明

  • artical_id-文章ID代表用户浏览的文章的ID,特殊情况artical_id-文章ID0表示用户在非文章内容页(比如App内的列表页、活动页等)。注意:只有artical_id为0时sign_in值才有效。
  • 从2021年7月7日0点开始,用户每天签到可以领1金币,并可以开始累积签到天数,连续签到的第3、7天分别可额外领2、6金币。
  • 每连续签到7天后重新累积签到天数(即重置签到天数:连续第8天签到时记为新的一轮签到的第一天,领1金币)

问题:计算每个用户2021年7月以来每月获得的金币数(该活动到10月底结束,11月1日开始的签到不再获得金币)。结果按月份、ID升序排序。

:如果签到记录的in_time-进入时间和out_time-离开时间跨天了,也只记作in_time对应的日期签到了。

输出示例:

示例数据的输出结果如下:

uidmonthcoin
10120210715
1022021107

解释:

101在活动期内连续签到了7天,因此获得1*7+2+6=15金币;

102在10.01~10.03连续签到3天获得5金币

10.04断签了,10.05~10.06连续签到2天获得2金币,共得到7金币。

解法:

关于连续第几天,其实只是一个小技巧,一点都不难理解

  1. 假设一组数据为[0,1,2,4,7,8],我们想按照连续性分组为[0,1,2]、[4]、[7,8]
  2. 我们先做序号列[1,2,3,4,5,6](通过RANK OVER(uid)实现)
  3. 然后序号-数据=[1,1,1,0,-2,-2],就是这么简单的技巧,得到能实现连续性分组的特征标签
  4. 按分组再加序号[1,2,3,1,1,2](通过RANK OVER(uid,特征标签) 实现
    
    with a as( -- 取出有效记录
        SELECT distinct uid,date(in_time) as dt -- 同一用户一天可能有多条日志记录,去重一下
        FROM tb_user_log
        where artical_id = 0 and sign_in = 1 and date(in_time) between '2021-07-07' and '2021-10-31'
    )
    ,b as( -- 得到组内分组标签
        select *,
            adddate(dt,INTERVAL - 
                rank() over (partition by uid order by dt)  -- 每个用户,把日期标上序号
                day) as group_tag  -- 用日期减序号,得到连续性分组
        from a
    )
    ,c as( -- 得出每天赚的金币数
        select uid,date_format(dt,'%Y%m') as month,
            case rank() over(partition by uid,group_tag order by dt)%7  -- 按连续性分组加序号,然后除以7求余数
                when 3 then 3  -- 每到第三天3枚
                when 0 then 7  -- 每到第七天7枚
                else 1 -- 其他天数1枚
            end as coin_eachday
        from b
    )
    select uid,month,sum(coin_eachday) as coin
    from c
    group by uid,month
    order by month,uid
    

  • 42
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值