掌柜又回来了😁今日带来👉512. Game Play Analysis II (游戏分析Ⅱ)的题解。
- 首先看题目:
- 解题思路👉: 此题要求的是每个玩家第一次登录日期的玩家ID 和他的设备ID。 那么我们首先要知道 每个玩家第一次登录的日期(其实就是511题:游戏分析Ⅰ) 是什么时候???接着用前面每个玩家的日期作为筛选条件进行查询就能得到相应的玩家ID 和 设备ID。
- 思路还是比较简单的,下面提供几个参考的解法👇:
- 第一个解法:子查询和聚合函数。
SELECT player_id, device_id FROM Activity
WHERE (player_id, event_date) IN (SELECT player_id, MIN(event_date) FROM Activity GROUP BY player_id);
此法应该是最好理解的,不过在我这里效率不是最优的:
于是掌柜又去翻看评论区和题解区,发现了另外两种主要的解法。
- 第二种解法:利用窗口函数生成新表再进行查询。
SELECT a.player_id, a.device_id
FROM (SELECT player_id, device_id, DENSE_RANK() OVER(PARTITION BY player_id ORDER BY event_date)date_rank FROM Activity) a
WHERE a.date_rank = 1;
这个解法也很好理解,按玩家ID分组,再按日期升序生成排名;只要排名等于1 的玩家就是第一天登录的玩家。不过效率也并没很大的提升:
- 最后一种解法: 利用原表和筛选后的新表进行内联来查询。
SELECT a.player_id,a.device_id
FROM Activity a JOIN (SELECT player_id,MIN(event_date) AS latest_date FROM Activity GROUP BY player_id) AS b
ON a.event_date = b.latest_date AND a.player_id = b.player_id;
这个解法可能大家一开始会不太容易想到,它是通过内联让新表查询到的日期等于原表的日期,并且玩家ID相等。最后效率掌柜这里是相对最优的(不知道为啥,同样的解法,掌柜这里的耗时总是更久。。。):
此题的题解到这里基本就结束了;不过掌柜好奇的查了一下为何这里的内联会更优于子查询???
(有兴趣的朋友可以直接往下看)
------------------------------------我是一条好奇的分割线-------------------------------------------
掌柜把该测试表格和数据都输入本地的MySQL进行测试,然后查询了他们各自的执行计划得到如下三张图。
第一张是使用子查询的:注意看掌柜圈住的那几个地方,其中两个查询子句的type 类型都是 ALL(表示走的是全表扫描,是效率最差的一种);所以你看后面的rows这一列行数就是全部;最后Extral表示额外信息(这里是使用了临时表)。
接下来看👉窗口函数:同样的type类型,第一个查询子句是ref(表示根据索引查找一个或多个值,这是效率较好的一种);然后你看它扫描的rows也只有1行;
但是看第二个子句的type类型就变成ALL,然后进行了全表扫描,而且最后的Extral还Using filesort(这表示使用了外部排序!!!这并不是一种效率高的方法)。所以窗口函数的执行时间也只是提升了一点点。
最后看两表内联:
首先 对 a 表type类型是ALL,即进行了全表扫描;
接着内联通过玩家ID 和登录日期进行查询,type类型变为了ref,只查询两行就结束了,并且在 Extral 列中使用了Using index(表明采用了索引进行覆盖所需的 SELECT 字段,好处就是:不需要进行回表,减少了数据查找的开销!);
最后Activity表type类型是ALL,使用的也是临时表。
整体下来 这里的内联效率稍微优于子查询,应该是使用索引的缘故。
不过也许是因为此题的测试数据量不是很大,所以这个是否真的都是使用内联查询会优于子查询?还有待进一步验证😅。
参考资料:
如何使用性能分析工具定位SQL执行慢的原因?