Bootstrap

【力扣 | SQL题 | 每日3题】力扣1097,1149,1070

1hard + 2 mid,难度还行,当做练手。

1. 力扣1097:游戏玩法分析5

1.1 题目:

表:Activity 

+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| player_id    | int     |
| device_id    | int     |
| event_date   | date    |
| games_played | int     |
+--------------+---------+
(player_id,event_date)是此表的主键(具有唯一值的列的组合)
这张表显示了某些游戏的玩家的活动情况
每一行表示一个玩家的记录,在某一天使用某个设备注销之前,登录并玩了很多游戏(可能是 0)

玩家的 安装日期 定义为该玩家的第一个登录日。

我们将日期 x 的 第一天留存率 定义为:假定安装日期为 X 的玩家的数量为 N ,其中在 X 之后的一天重新登录的玩家数量为 MM/N 就是第一天留存率,四舍五入到小数点后两位

编写解决方案,报告所有安装日期、当天安装游戏的玩家数量和玩家的 第一天留存率

以 任意顺序 返回结果表。

结果格式如下所示。

示例 1:

输入:
Activity 表:
+-----------+-----------+------------+--------------+
| 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-01 | 0            |
| 3         | 4         | 2016-07-03 | 5            |
+-----------+-----------+------------+--------------+
输出:
+------------+----------+----------------+
| install_dt | installs | Day1_retention |
+------------+----------+----------------+
| 2016-03-01 | 2        | 0.50           |
| 2017-06-25 | 1        | 0.00           |
+------------+----------+----------------+
解释:
玩家 1 和 3 在 2016-03-01 安装了游戏,但只有玩家 1 在 2016-03-02 重新登录,所以 2016-03-01 的第一天留存率是 1/2=0.50
玩家 2 在 2017-06-25 安装了游戏,但在 2017-06-26 没有重新登录,因此 2017-06-25 的第一天留存率为 0/1=0.00

1.2 思路:

看到第一天条件反射就是窗口函数,然后判断是用rank(),dense_rank(), row_number()。

1.3 题解:

-- 第一天=>排名第一=>窗口函数=>row_number
with tep1 as (
    select player_id , event_date , row_number() over (partition by player_id order by event_date) ranks
    from Activity
), tep2 as (
    -- 查询到所有第一个登陆日的玩家
    select *
    from tep1
    where ranks = 1
)
-- 然后以日期分组,installs即是该天第一个登陆日的玩家总数
select event_date install_dt, count(*) installs,
round(
    (
        -- 下面的子查询的意思是:
        -- 该第一天的下一天且这个玩家在第一天出现过,并且ranks排第二,说明是第二天重新登录的玩家
        -- where的三个条件才能限定第二天重新登录的数量。
        -- 然后除以第一天登录的玩家总数,并取两个小数
        select count(*)
        from tep1 t2
        where datediff(t1.event_date, t2.event_date) = -1 and ranks = 2
        and player_id in (
            select player_id
            from tep2 t3
            where datediff(t3.event_date, t2.event_date) = -1 and ranks = 1
        )
    ) / count(*), 2
) Day1_retention
from tep2 t1
group by event_date
-- 虽然题目并不要求排序,但排序后明显时间缩短,效率提高
-- 因为答案输出给的表是顺序的
order by event_date

2. 力扣1149:文章浏览2

2.1 题目:

表: Views

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| article_id    | int     |
| author_id     | int     |
| viewer_id     | int     |
| view_date     | date    |
+---------------+---------+
此表可能会存在重复行。
此表的每一行都表示某人在某天浏览了某位作者的某篇文章。 
请注意,同一人的 author_id 和 viewer_id 是相同的。

编写解决方案来找出在同一天阅读至少两篇文章的人。

结果按照 id 升序排序。

结果的格式如下。

示例 1:

输入:
Views 表:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date  |
+------------+-----------+-----------+------------+
| 1          | 3         | 5         | 2019-08-01 |
| 3          | 4         | 5         | 2019-08-01 |
| 1          | 3         | 6         | 2019-08-02 |
| 2          | 7         | 7         | 2019-08-01 |
| 2          | 7         | 6         | 2019-08-02 |
| 4          | 7         | 1         | 2019-07-22 |
| 3          | 4         | 4         | 2019-07-21 |
| 3          | 4         | 4         | 2019-07-21 |
+------------+-----------+-----------+------------+
输出:
+------+
| id   |
+------+
| 5    |
| 6    |
+------+

2.2 思路:

注意到为什么要使用两个distinct。

2.3 题解:

-- 先在原表过滤同一天阅读几篇相同的文章的情况,所以要去重
with tep as (
    select distinct article_id, author_id, viewer_id, view_date
    from Views
)

-- 然后根据view_date, viewer_id分组
-- 为什么要去重:打个比方:人物A在19号这天阅读了两篇文章,又在20号这天
-- 阅读了两篇文章,所以会查询到两个相同的记录A,所以要去重。
select distinct viewer_id id
from tep
group by view_date, viewer_id
having count(*) >= 2
order by id

3. 力扣1070:产品销售分析3

3.1 题目:

销售表 Sales

+-------------+-------+
| Column Name | Type  |
+-------------+-------+
| sale_id     | int   |
| product_id  | int   |
| year        | int   |
| quantity    | int   |
| price       | int   |
+-------------+-------+
(sale_id, year) 是这张表的主键(具有唯一值的列的组合)。
product_id 是产品表的外键(reference 列)。
这张表的每一行都表示:编号 product_id 的产品在某一年的销售额。
请注意,价格是按每单位计的。

产品表 Product

+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| product_id   | int     |
| product_name | varchar |
+--------------+---------+
product_id 是这张表的主键(具有唯一值的列)。
这张表的每一行都标识:每个产品的 id 和 产品名称。

编写解决方案,选出每个售出过的产品 第一年 销售的 产品 id年份数量 和 价格

结果表中的条目可以按 任意顺序 排列。

结果格式如下例所示:

示例 1:

输入:
Sales 表:
+---------+------------+------+----------+-------+
| sale_id | product_id | year | quantity | price |
+---------+------------+------+----------+-------+ 
| 1       | 100        | 2008 | 10       | 5000  |
| 2       | 100        | 2009 | 12       | 5000  |
| 7       | 200        | 2011 | 15       | 9000  |
+---------+------------+------+----------+-------+
Product 表:
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 100        | Nokia        |
| 200        | Apple        |
| 300        | Samsung      |
+------------+--------------+
输出:
+------------+------------+----------+-------+
| product_id | first_year | quantity | price |
+------------+------------+----------+-------+ 
| 100        | 2008       | 10       | 5000  |
| 200        | 2011       | 15       | 9000  |
+------------+------------+----------+-------+

3.2 思路:

-- 第一年=> 排名第一 => 窗口函数 => dense_rank

3.3 题解:

-- 第一年=> 排名第一 => 窗口函数 => dense_rank
-- 因为第一年可能有多条记录,所以用dense_rank
with tep as (
    select product_id , year , quantity, price, dense_rank() over (partition by product_id order by year) ranks
    from Sales
)
select product_id, year first_year, quantity, price
from tep
where ranks = 1

;