Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

49 活跃用户 #59

Open
astak16 opened this issue Mar 5, 2022 · 0 comments
Open

49 活跃用户 #59

astak16 opened this issue Mar 5, 2022 · 0 comments
Labels

Comments

@astak16
Copy link
Owner

astak16 commented Mar 5, 2022

题目

题目连接:活跃用户

写一个 SQL 查询, 找到活跃用户的 idname

活跃用户是指那些至少连续 5 天登录账户的用户。

create table accounts (id int, name varchar(10));
insert into accounts (id, name) values
(1, 'Winston'),
(7, 'Jonathan');

create table logins (id int, login_date date);
insert into logins (id, login_date) values
(7, '2020-05-30'),
(1, '2020-05-30'),
(7, '2020-05-31'),
(7, '2020-06-01'),
(7, '2020-06-02'),
(7, '2020-06-02'),
(7, '2020-06-03'),
(1, '2020-06-07'),
(7, '2020-06-10');

SQL:方法一

select
  distinct id, name
from (
  select
	  id,
    login_date,
	  datediff(lead(login_date, 4) over(partition by id order by login_date), login_date) diff
  from logins group by id, login_date
) temp left join accounts using(id) where diff = 4

解析

  1. 去重,因为一名用户可能会在一天当中登录多次

    select id, login_date from logins group by id, login_date
  2. 使用 lead 窗口函数,向下 4

    lead(login_date, 4) over(partition by id order by login_date)
    
    -- 结果
    id  |  login_date  |  lead_date
    1   |  2020-05-30  |  null
    1   |  2020-06-07  |  null
    7   |  2020-05-30  |  2020-06-03
    7   |  2020-05-31  |  2020-06-10
    7   |  2020-06-01  |  null
    7   |  2020-06-02  |  null
    7   ]  2020-06-03  |  null
    7   |  2020-06-10  |  null
  3. 使用 datediff 计算 login_datelead_date 之间的时间差

  4. 将上面的结果作为临时表 temp

  5. 将临时表 tempaccounts 左连,筛选出第三步中的时间差为 4 的用户

SQL:方法二

select
	distinct id, name
from accounts join (
  select
    id,
		@rn:=if(@pre_id=id and @pre_login_date = subdate(login_date, interval 1 day), @rn + 1, 1) rn,
		@pre_id:=id,
		@pre_login_date:=login_date
  from (
    select id, login_date from logins group by id, login_date
  ) t1, (select @pre_id:=null, @pre_login_date:=null, @rn:=0) init
) t2 using(id) where t2.rn >= 5 order by accounts.id;

解析

使用变量对将每个用户的登录日期(去重)按照先后顺序进行排名,

  • 比较当前数据的 id 和 上一条数据的 id 是否相等
  • subdate 比较当前数据的 login_date 和上一条数据的 login_date 是否相等
  1. 各变量初始化

    • @pre_id 保存上一条数据的 id, 初始化为 null
    • @pre_login_date 保存上一条数据的 login_date, 初始化为 null
    • @rn 保存媒体数据的排名,初始化为 0
    select @pre_id:=null, @pre_login_date:=null, @rn:=0
  2. 使用 if 进行判断

    • 如果 @pre_id (上一条数据的 id) 等于当前这条数据的 id ,并且 @pre_login_date (上一条数据的 login_date)等于当前这条数据的 login_date ,输出@rn + 1 ,否则输出1
      • 运行第一条数据时, @pre_id = null@pre_login_date = null
        • @pre_id = idnull = 7false
        • @pre_login_date = subdate(login_date, interval 1 day)null = '2020-05-29'false
        • 输出为 1
        • @pre_id 赋值为当前数据的 id ,结果是 @pre_id:=7@pre_login_date 赋值为当前数据的 login_date,结果是 @pre_login_date:= '2020-05-30'
      • 运行第二条数据时,@pre_id = 7@pre_login_date = '2020-05-30'
        • @pre_id = id7 = 7true
        • @pre_login_date = subdate(login_date, interval 1 day)'2020-05-30' = '2020-05-30'true
        • 输出 @rn + 12
        • @pre_id 赋值为当前数据的 id ,结果是 @pre_id:=7@pre_login_date 赋值为当前数据的 login_date,结果是 @pre_login_date:= '2020-05-31'
      • 以此类推,运行第三条数据,直到运行到最后一条数据
    if(@pre_id=id and @pre_login_date = subdate(login_date, interval 1 day), @rn + 1, 1) rn
  3. 将步骤一和步骤二的结果作为临时表 t2

  4. 查询临时表 t2 筛选出 rn 大于 5 的数据。

SQL:方法三

select
	distinct id, name
from (
  select
	  id,
	  subdate(login_date, rn) first_login_date
  from (
    select
      id,
      login_date,
      row_number() over(partition by id order by login_date) rn
    from (
	    select
        id,
        login_date
      from logins group by id, login_date order by id, login_date
    ) t1
  ) t2 group by id, first_login_date having count(*) >= 5
) t3 left join accounts using(id);

解析

使用窗口函数对将每个用户的登录日期(去重)按照先后顺序进行排名,再用 subdate 计算出第一天登录日期

  1. 使用 row_number() ,将每个用户按照登录日期进行排序,因为一个用户可以一天登录多次,所以所以首先要对 logins.login_date 进行去重

    select
      id,
      login_date,
      row_number() over(partition by id order by login_date) rn
    from (
      select id, login_date from logins group by id, login_date
    ) t1
  2. 使用 subdate 计算每个日期的第一次登录的日期

    • 计算一段连续的数:用一个数(日期)减去它的排名,会得到一个相同的数
    select
      id,
      subdate(login_date, rn) first_login_date
    from (
      select
        id,
        login_date,
        row_number() over(partition by id order by login_date) rn
      from (
        select id, login_date from logins group by id, login_date
      ) t1
    ) t2
  3. 按照 idfirst_login_date 进行分组,筛选出大于 5 天的用户

  4. 连接 accounts ,再对 accounts.idaccounts.name 进行去重

    1. 去重是因为,某一个用户可能会有几个连续登录 5 天的区间

SQL:方法四

select
	distinct accounts.*
from accounts
join logins l1 using(id)
join logins l2 on l1.id = l2.id and datediff(l2.login_date, l1.login_date) between 0 and 4
group by accounts.id, accounts.name, l1.login_date
having count(distinct l2.login_date) = 5;

解析

自连接, l1.login_date 作为登录起始日, l2.login_date 用来和 l1.login_date 进行比较,也就是说每个日期都检查之后是否有 4 个连续日期

  1. datediff(l2.login_date, l1.login_date) 进行比较,是否在 04 天之间

    • datediff('2022-01-02', '2022-01-02') // 0
    • datediff('2022-01-03', '2022-01-02') // 1
    • datediff('2022-01-04', '2022-01-02') // 2
    • datediff('2022-01-05', '2022-01-02') // 3
    • datediff('2022-01-06', '2022-01-02') // 4

    那你可能会想到可不可以用 between 1 and 5 ,那我们来看一下 1 ~ 5 的结果是啥

    • datediff('2022-01-03', '2022-01-02') // 1
    • datediff('2022-01-04', '2022-01-02') // 2
    • datediff('2022-01-05', '2022-01-02') // 3
    • datediff('2022-01-06', '2022-01-02') // 4
    • datediff('2022-01-07', '2022-01-02') // 5

    l2.login_datel1.login_date 开始日后(包括开始日)连续登录日期,如果 l2.login_date 不包括开始日,后面处理就麻烦了。

    select
      *
    from logins l1 join logins l2
    on l1.id = l2.id and datediff(l2.login_date, l1.login_date) between 0 and 4;
  2. accountsl1l2 连接,并且按照 accounts.idaccounts.namel1.login_date 进行分组。

    • 为什么这里使用 l1.login_date ,因为第一步中计算 l1.login_date 后是否有 4 个连续日期
  3. 使用 having 筛选出 l2.login_date 等于 5 数据(需要使用 distinct 去重)

    • 用户可以在一天之内登录多次,所有 l2.login_date 不去重的话,不符合题目要求
@astak16 astak16 added the 中等 label Mar 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant