刷题记录 | 牛客网 SQL 编程题

1. 刷题通过的题目排名

select id, number, dense_rank()over(order by number desc) as rank
from passing_number
order by number desc, id asc;

注意:题目要求的排名是 1 2 2 3 稠密排名,所以用 dense_rank() 函数。

2. 出现三次以上积分相同的number

select number from grade
group by number
having count(number) >= 3;

3. 找到每个人的任务

select p.id, p.name, t.content
from person p
left join task t
on p.id = t.person_id
order by p.id asc;

题目要求person表中所有的id都要有,所以这个时候用 left join。

4. 每门课程考试前2名的学生

select id, name, score 
from
( select g.id, l.name, g.score, dense_rank() over( partition by language_id order by score desc) as ranking
   from grade g 
   join language l 
   on g.language_id = l.id
)
where ranking <= 2
order by  name,score desc, id;

思路:既然考到了名次,那么首先想到的是用窗口函数中的排名函数。如果取前二名,直接用where判断即可。
注意:

  • 题目给出排序的条件有三个。注意写法。
  • 这里的排名函数只有用 dense_rank() 的时候才给通过,其余两个都会报错,大概是要考虑了分数相同的两个人要属于同一个名次,且不影响第二名。

5. 邮件异常的概率

要计算每天邮件异常的概率。条件是正常用户发给正常用户中成功的,所以要关联表,提出掉黑名单里的用户。

select t.date, 
       round(sum( case when t.type='no_completed' then 1 else 0 end)*1.0 / count(*),3) as p 
from email t
where t.send_id in (
        select id 
        from user
        where is_blacklist = 0
        )
and t.receive_id in (
        select id 
        from user
        where is_blacklist = 0
        )
group by t.date
order by t.date asc;

解法一:是用where条件来筛出符合条件的用户的。当然也可以用 inner join 来筛,方法如下:

select email.date, round(
    sum(case email.type when'completed' then 0 else 1 end)*1.0/count(email.type),3
) as p
from email
join user as u1 on (email.send_id=u1.id and u1.is_blacklist=0)
join user as u2 on (email.receive_id=u2.id and u2.is_blacklist=0)
group by email.date order by email.date;

6. 牛客网每个用户最近一次登录的日期

用排名函数就可以解决。

select t.date
from 
	(
	select user_id, date, row_number() over( partition by user_id order by date desc) as rk
	from login
	) t
where t.rk = 1
order by t.user_id asc;

7. 牛客每个人最近一次登录 ②

相比于上一道题,这道题要求查询出对应的用户名和设备名,其实也简单,用两个 join 连接就可以搞定。

select t.u_n, t.c_n, date as d 
from 
    (select l.user_id,  u.name as u_n, c.name as c_n, l.date, row_number() over(partition by l.user_id order by l.date desc) as rk
    from login l
    join user u 
    on l.user_id = u.id
    join client c 
    on l.client_id = c.id) t
where rk = 1
order by t.u_n asc;

8. 牛客新用户的次日留存率

select round(count(login.user_id) * 1.0/count(a.user_id), 3) as p
from (
	select user_id, min(date) as date
	from login
	group by user_id ) a
left join login
on login.user_id = a.user_id
and login.date = date(a.date, '+1 day')

注:这是一道计算次日留存率的题目,也是面试和笔试考察的重点内容。有一定的难度,需要重点关注一下。

9. 统计每日新登录的用户数

这道题的解法思路还挺巧的,对用户进行分组并按照日期排名,取排名全为 1 的,即是首次登陆的用户,然后再按照日期分组即可。

select a.date,
	   sum(case when rk = 1 then 1 else 0 end) new
from (
	select 
		user_id, 
		date, 
		row_number() over(partition by user_id order by date asc) as rk
	from login
	) a
group by date;

10. 计算每天新用户的次日留存率

这道题的解法其实和第8道很像,但是唯一还需要考虑的是没有新增用户的日期。因此需要用到 union 函数。

select a.date, 
	   round(count(login.user_id) * 1.0 / count(a.user_id), 3) as p
from 
	(
	select user_id, min(date) as d
	from login
	group by user_id ) as a
left join login
on login.user_id = a.user_id
and login.date = date(a.date, '+1 day')
group by a.date   # 要看每天的留存,所以这一步不能少
union 
select date, 0.000 as p 
from login
where date not in (    # 没有 is 直接 not in 
	select min(date) as date
	from login
	group by user_id)
order by date;

11. 统计累计刷题信息

这道题需要注意是每天累计的通过题目的数量,用窗口函数 + 连接查询可以实现。

select 
    u.name, 
    c.name, 
    pn.date, 
    sum(pn.number) over( partition by pn.user_id order by pn.date) as ps_num 
    # 按照用户进行分类,看每一个用户在date下的累计刷题数目。
from passing_number pn
left join user u 
on pn.user_id = u.id
left join login
on login.user_id = pn.user_id
and login.date = pn.date
left join client c 
on login.client_id = c.id
order by pn.date, u.name;

计算留存率终极代码

select *,
concat(round(100*次日留存用户/日新增用户数,2),'%')  次日留存率,
concat(round(100*三日留存用户/日新增用户数,2),'%')  三日留存率,
concat(round(100*七日留存用户/日新增用户数,2),'%')  七日留存率,
concat(round(100*三十日留存用户/日新增用户数,2),'%')  三十日留存率 
from 
(	
	select 
	c.log_day 日期,
	count(distinct c.u_id)  日新增用户数,
	count(distinct d.u_id)  次日留存用户,
	count(distinct e.u_id)  三日留存用户,
	count(distinct f.u_id)  七日留存用户,
	count(distinct g.u_id)  三十日留存用户
	from 
	(
		-- 确保是新增用户
		select a.*
		from user_login a 
		left join user_login b on a.u_id = b.u_id and b.log_day < a.log_day
		where b.log_day is null
	) c
	left join user_login d on c.u_id = d.u_id  and  DATEDIFF(d.log_day,c.log_day) = 1 
	left join user_login e on c.u_id = e.u_id  and  DATEDIFF(e.log_day,c.log_day) = 3
	left join user_login f on c.u_id = f.u_id  and  DATEDIFF(f.log_day,c.log_day) = 7
	left join user_login g on c.u_id = g.u_id  and  DATEDIFF(g.log_day,c.log_day) = 30
	group by c.log_day
) p;

你可能感兴趣的:(SQL,数据库,mysql,数据分析)