SQL练习——(15/81)
目录
1.计算次日留存率
2.多条件查询
方法1:子查询
方法2:窗口函数实现
3.条件查询——自连接相关
1.计算次日留存率
550. 游戏玩法分析 IV - 力扣(LeetCode)
错误查询1:(没有考虑从首次登录日期开始至少连续两天登录)
遇到了一个和distinct有关的问题
--查询1
WITH second_login AS (SELECT DISTINCT player_id FROM (SELECT player_id, event_date,LAG(event_date, 1) OVER (PARTITION BY player_id ORDER BY event_date) AS pre_dataFROM activity) AS subqueryWHERE DATEDIFF(event_date, pre_data) = 1
)
SELECT ROUND(COUNT(second_login.player_id) / COUNT(DISTINCT activity.player_id), 2) AS fraction
FROM second_login, activity;--查询2
WITH second_login AS (SELECT player_id FROM (SELECT player_id, event_date,LAG(event_date, 1) OVER (PARTITION BY player_id ORDER BY event_date) AS pre_dataFROM activity) AS subqueryWHERE DATEDIFF(event_date, pre_data) = 1
)
SELECT ROUND(COUNT(DISTINCT second_login.player_id) / COUNT(DISTINCT activity.player_id), 2) AS fraction
FROM second_login, activity;
查询1:
-
second_login
子查询返回的player_id
是唯一的,因为DISTINCT
在内层SELECT
语句中使用。 -
在最终的
SELECT
语句中,COUNT(second_login.player_id)
直接计算所有player_id
的数量,而不会去除重复项。 -
意味着如果
second_login
子查询返回了重复的player_id
,这些重复项不会被去除。
查询2:
-
在最终的
SELECT
语句中,COUNT(DISTINCT second_login.player_id)
确保只计算唯一的player_id
。 -
意味着即使
second_login
子查询返回了重复的player_id
,最终的计数也会去除这些重复项。
感觉按理说两个查询效果应该是一样的,但是2统计的结果是比1少的,问AI也解释不清楚。。。
错误查询2:超出时间限制
with first_login as(select player_id,min(event_date) as first_datafrom activitygroup by player_id
),second_login as(select activity.player_idfrom activityjoin first_login on first_login.player_id=activity.player_idwhere datediff(activity.event_date,first_login.first_data)=1
)
select round(count(distinct second_login.player_id)/count(distinct activity.player_id),2) as fraction
from second_login,activity
-
在最终的
SELECT
语句中,second_login
和activity
表通过笛卡尔积连接,这会导致second_login
表中的每一行与activity
表中的每一行进行比较,从而产生大量的重复计算。 -
这种操作在大数据集上非常耗时,可能导致查询超时。
正确的查询:(写的够呛T^T)
select ifNULL(round(count(distinct secondlogin.player_id)/count(distinct activity.player_id),2),0) as fraction
from (select activity.player_id as player_id from(select player_id,date_add(min(event_date),interval 1 day) as second_datefrom activitygroup by player_id) as expectedlogin,activitywhere activity.event_date=expectedlogin.second_date and activity.player_id=expectedlogin.player_id
) as secondlogin,activity
内部子查询:为每个玩家计算出他们第一次登录的日期加一天的日期(second_date
),即预期的第二次登录日期。
-
min(event_date)
:找到每个玩家的最早登录日期。 -
date_add(min(event_date), interval 1 day)
:将最早登录日期加一天,得到预期的第二次登录日期。 -
group by player_id
:按玩家分组,确保每个玩家只计算一次。
外部子查询:找出实际在预期的第二次登录日期登录的玩家。
-
activity
表与expectedlogin
子查询进行连接,条件是activity.event_date=expectedlogin.second_date
和activity.player_id=expectedlogin.player_id
。 -
这样筛选出的
player_id
就是实际在预期的第二次登录日期登录的玩家。
总结:
-
次日留存率:计算第二天登录的玩家占总玩家的比例。
-
关键步骤:(嵌套子查询)
-
计算每个玩家的预期第二次登录日期。
-
找出实际在预期第二次登录日期登录的玩家。
-
计算次日留存率。
-
-
SQL知识点:
-
子查询
-
COUNT(DISTINCT ...)
-
ROUND(..., 2)
-
IFNULL(..., 0)
-
DATE_ADD(..., INTERVAL 1 DAY)
-
JOIN
操作/where操作
-
2.多条件查询
方法1:子查询
select round(sum(tiv_2016),2) as tiv_2016
from (select tiv_2016 from insurancewhere tiv_2015 in (select tiv_2015from insurancegroup by tiv_2015having count(*) >1)and (lat,lon) in(select lat,lonfrom insurancegroup by lat,lonhaving count(*)=1)
) as subquery
-
子查询1:
SELECT tiv_2015 FROM insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1
:筛选出2015年投保额至少跟一个其他投保人相同的投保额。 -
子查询2:
SELECT lat, lon FROM insurance GROUP BY lat, lon HAVING COUNT(*) = 1
:筛选出所在城市与其他投保人都不同的投保人的(lat, lon)
。 -
主查询:
SELECT tiv_2016 FROM insurance WHERE tiv_2015 IN (...) AND (lat, lon) IN (...)
:筛选出同时满足上述两个条件的投保人的tiv_2016
。
方法2:窗口函数实现
更高效,使用窗口函数可以避免多次扫描表,提高查询效率
with subquery as (select*,sum(1) over (partition by tiv_2015) as same_tiv_2015_num,sum(1) over (partition by concat(lat, '-', lon)) as same_position_numfrom Insurance
)
select round(sum(tiv_2016), 2) as tiv_2016
from subquery
where same_tiv_2015_num > 1 and same_position_num = 1
-
SUM(1) OVER (PARTITION BY tiv_2015)
:-
对每个
tiv_2015
分组,计算每个分组中的记录数。 -
same_tiv_2015_num
表示每个投保人在2015年的投保额与多少其他投保人相同。
-
-
SUM(1) OVER (PARTITION BY CONCAT(lat, '-', lon))
:-
对每个
(lat, lon)
分组,计算每个分组中的记录数。 -
same_position_num
表示每个投保人的位置(纬度和经度)与其他投保人是否相同。 -
使用
CONCAT(lat, '-', lon)
将纬度和经度组合成一个字符串,以便进行分组。
-
3.条件查询——自连接相关
(自连接join、分组统计group、条件过滤having/where)
570. 至少有5名直接下属的经理 - 力扣(LeetCode)
select e2.name
from employee e2
left join employee e1 on e1.managerID = e2.id
group by e2.id
having count(e1.managerID) >=5
577. 员工奖金 - 力扣(LeetCode)
select employee.name,bonus.bonus
from employee
left join bonus on bonus.empId=employee.empId
where bonus.bonus<1000 or bonus.bonus is null
584. 寻找用户推荐人 - 力扣(LeetCode)
SELECT name
FROM customer
-- where referee.id !=2
WHERE referee_id IS NULL OR referee_id != 2;
NULL
不能直接用=
或!=
来比较,必须使用IS NULL
或IS NOT NULL
。