详解力扣高频SQL50题之550. 游戏玩法分析 IV【中等】

发布于:2025-07-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

传送门:550. 游戏玩法分析 IV

题目

Table: Activity

±-------------±--------+
| Column Name | Type |
±-------------±--------+
| player_id | int |
| device_id | int |
| event_date | date |
| games_played | int |
±-------------±--------+
(player_id,event_date)是此表的主键(具有唯一值的列的组合)。
这张表显示了某些游戏的玩家的活动情况。
每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0)。
编写解决方案,报告在首次登录的第二天再次登录的玩家的 比率,四舍五入到小数点后两位。换句话说,你需要计算从首次登录后的第二天登录的玩家数量,并将其除以总玩家数。

结果格式如下所示:

示例 1:

输入:
Activity table:
±----------±----------±-----------±-------------+
| player_id | device_id | event_date | games_played |
±----------±----------±-----------±-------------+
| 1 | 2 | 2016-03-01 | 5 |
| 1 | 2 | 2016-03-02 | 6 |
| 2 | 3 | 2017-06-25 | 1 |
| 3 | 1 | 2016-03-02 | 0 |
| 3 | 4 | 2018-07-03 | 5 |
±----------±----------±-----------±-------------+
输出:
±----------+
| fraction |
±----------+
| 0.33 |
±----------+
解释:
只有 ID 为 1 的玩家在第一天登录后才重新登录,所以答案是 1/3 = 0.33

解析

这题对MySQL选手来说是中等难度,对Oracle选手却是困难,后面会讲原因。
题目说了第二天登录,所以要再关联一张表,该表的日期都是第一天的日期即最小日期,然后让原表的日期=最小日期+1。具体来说,首先要对玩家id分组,再用min()求出最小日期,再和其他字段作为一张表,再与原表连接,连接条件是原表.id=最小日期表id且原表的日期=最小日期+1,这样就筛出第二天登录的玩家信息了,然后玩家总数用子查询统计,子查询查询原表的不重复的玩家id数。最后,用count()除以子查询结果*100即所求。注意日期+1和聚合函数除以子查询在不同数据库实现不同,mysql的日期+1必须用dateadd()函数,允许聚合函数除以子查询,但oracle可直接用日期+1,不允许在未分组的情况下使用聚合函数除以子查询,接下来先给出mysql解法,再给出oracle解法。

算法(MySQL)

查询活动表,对玩家id分组,再用min()求出最小日期,用count()计算玩家数,再和其他字段作为一张表,再与原表连接,连接条件是原表的日期=最小日期+1,用dateadd()实现。连接完后,用count()统计连续登录两天的玩家数,用子查询统计玩家总数,其中子查询用count()+distinct统计原表的玩家总数,最后用连续登录两天的玩家数/玩家总数*100即连续登录率,返回该字段即可。

代码(MySQL)

select round(count(*)/
(select count(distinct player_id) from Activity),2)as fraction
from(select player_id,min(event_date) as first_date
from Activity
group by player_id)t
join Activity a
on a.player_id=t.player_id and a.event_date = DATE_ADD(t.first_date, INTERVAL 1 DAY);

以下内容MySQL选手可以不用看了,Oracle选手必看。

解析(Oracle)

核心算法前面讲过,不再赘述,这里讲Oracle实现。首先,直接用count()/(select count()…)会报错:ORA-00937: not a single-group group function,意思就是若不分组,非聚合字段是不允许和聚合函数一起使用的,也就是要么用group by,要么对字段聚合,这里group by未必要对子查询分组,对任何字段都可以,比如玩家id,但我们要求的是表中所有行数,所以不能用group by。所以只能对子查询使用聚合函数,满足要么都聚合,要么分组+聚合的语法规则。聚合函数只要能保持原来的值就行,比如min(),max(),这里取max()。但是这还没结束,因为Oracle中外层查询和子查询是相关的,外层查询的来源表,即外层from后的表为空,则任何子查询为空,一定返回NULL,而MySQL子查询是和外层查询独立的,即便外层查询的来源表为空,也不影响子查询返回数据。所以当不存在连续登录两天的玩家时,在Oracle中就需要用NVL处理NULL值,要么对count()/max()的结果使用NVL,要么对round()使用NVL,至此,Oracle实现难点才算结束!

代码(Oracle)

select round(NVL(count(*)/
max((select count(distinct player_id) from Activity)),0),2)
as fraction
from(select player_id,min(event_date) as first_date
from Activity
group by player_id)t
join Activity a
on a.player_id=t.player_id and a.event_date=t.first_date+1

网站公告

今日签到

点亮在社区的每一天
去签到