Mysql疑难题的记录(力扣)
出过错的题目:
574当选者
534. 游戏玩法分析 III(求累加值)
sql架构
Create table If Not Exists Activity (player_id int, device_id int, event_date date, games_played int) Truncate table Activity insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-03-01', '5') insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-05-02', '6') insert into Activity (player_id, device_id, event_date, games_played) values ('1', '3', '2017-06-25', '1') insert into Activity (player_id, device_id, event_date, games_played) values ('3', '1', '2016-03-02', '0') insert into Activity (player_id, device_id, event_date, games_played) values ('3', '4', '2018-07-03', '5')
这张表显示了某些游戏的玩家的活动情况。
每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0 )
问题:
编写一个 SQL 查询,同时报告每组玩家和日期,以及玩家到目前为止玩了多少游戏。也就是说,在此日期之前玩家所玩的游戏总数。
Result table: +-----------+------------+---------------------+ | player_id | event_date | games_played_so_far | +-----------+------------+---------------------+ | 1 | 2016-03-01 | 5 | | 1 | 2016-05-02 | 11 | | 1 | 2017-06-25 | 12 | | 3 | 2016-03-02 | 0 | | 3 | 2018-07-03 | 5 | +-----------+------------+---------------------+ 对于 ID 为 1 的玩家,2016-05-02 共玩了 5+6=11 个游戏,2017-06-25 共玩了 5+6+1=12 个游戏。 对于 ID 为 3 的玩家,2018-07-03 共玩了 0+5=5 个游戏。 请注意,对于每个玩家,我们只关心玩家的登录日期。
解法1:
select t1.player_id,t1.event_date,sum(t2.games_played) games_played_so_far from Activity t1 inner join Activity t2 on t1.player_id = t2.player_id and t1.event_date >= t2.event_date group by t1.player_id,t1.event_date order by t1.player_id,t1.event_date
思路:
我们求的是每个玩家从第一天开始,到最后一天结束。每天统计:当天玩游戏次数+之前的所有天的玩游戏次数的总和。
首先,使用笛卡尔积,自连2个表。
然后,条件筛选,t1.event_date >= t2.event_date。⚠️这个条件非常重要!理解它就理解了本题答案。
- t1.event_date > t2.event_date:
- 如果t1.event_date是day_one,那么筛选表中只有一条数据是关于t1.day_one的。
- 如果t1.event_date是day_two, 那么筛选表中会有2行相关t1.day_two的数据,这两行数据的区别在t2.event_date: 一个是day_one, 一个是day_two.
- 如果t1.event_date是day_three,那么筛选表中会有3行相关t1.day_three的数据。。。
- 同理可知之后的情况。
那么,以t1.even_date进行分组的话,sum(t2.games_played)就相当于累加了当天及之前数天的玩游戏的次数。
总结:
将每个人,在当前和之前几天玩过的游戏个数进行累加。
2个表自连接,筛选条件,group by。 使用累加和的方法解题。
另一种解法:
使用Mysql8.0的window计算函数。
select player_id, event_date, sum(games_played) over(partition by player_id order by event_date asc rows UNBOUNDED PRECEDING ) as games_played_so_far from activity;
+-----------+------------+---------------------+ | player_id | event_date | games_played_so_far | +-----------+------------+---------------------+ | 1 | 2016-03-01 | 5 | | 1 | 2016-05-02 | 11 | | 1 | 2017-06-25 | 12 | | 3 | 2016-03-02 | 0 | | 3 | 2018-07-03 | 5 | +-----------+------------+---------------------+
window的frame语法使用:https://www.cnblogs.com/chentianwei/p/12145280.html
⬆️代码也可以用
- range UNBOUNDED PRECEDING
- range between UNBOUNDED PRECEDING and current row
- rows between UNBOUNDED PRECEDING and current row
⚠️好像range|rows是别名的关系。
更复杂的方法:
使用if语句和@var进行逻辑判断和逻辑运算。不适合mysql5.7。分析不复杂,但代码写起来很复杂。
626. 换座位 (奇偶判断)
sql架构
Create table If Not Exists seat(id int, student varchar(255)) Truncate table seat insert into seat (id, student) values ('1', 'Abbot') insert into seat (id, student) values ('2', 'Doris') insert into seat (id, student) values ('3', 'Emerson') insert into seat (id, student) values ('4', 'Green') insert into seat (id, student) values ('5', 'Jeames')
小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。
其中纵列的 id 是连续递增的
小美想改变相邻俩学生的座位。
你能不能帮她写一个 SQL query 来输出小美想要的结果呢?
+---------+---------+ | id | student | +---------+---------+ | 1 | Abbot | | 2 | Doris | | 3 | Emerson | | 4 | Green | | 5 | Jeames | +---------+---------+ #假如数据输入的是上表,则输出结果如下: +---------+---------+ | id | student | +---------+---------+ | 1 | Doris | | 2 | Abbot | | 3 | Green | | 4 | Emerson | | 5 | Jeames |
有2种解题方法:常用的这种:
-- 无需启用事务,因为只是对查询的表改结果 -- 只改id值即可,如果id是奇数,则➕1变成偶数;如果id是偶数,则-1变成奇数。 -- 最后的id,如果是奇数则不变。 select (case when id % 2 != 0 and id != @last_id then id + 1 when id % 2 != 0 and id = @last_id then id else id - 1 end) as id, student from seat, (select @last_id := count(1) from seat) t order by id
还有一种使用位运算xor, 或者^符号,进行按位异或运算,使用这个公式(i+1)^1-1,可以交换相邻的id。
601. 体育馆的人流量
Create table If Not Exists stadium (id int, visit_date DATE NULL, people int) Truncate table stadium insert into stadium (id, visit_date, people) values ('1', '2017-01-01', '10') insert into stadium (id, visit_date, people) values ('2', '2017-01-02', '109') insert into stadium (id, visit_date, people) values ('3', '2017-01-03', '150') insert into stadium (id, visit_date, people) values ('4', '2017-01-04', '99') insert into stadium (id, visit_date, people) values ('5', '2017-01-05', '145') insert into stadium (id, visit_date, people) values ('6', '2017-01-06', '1455') insert into stadium (id, visit_date, people) values ('7', '2017-01-07', '199') insert into stadium (id, visit_date, people) values ('8', '2017-01-08', '188')
X 市建了一个新的体育馆,每日人流量信息被记录在这三列信息中:序号 (id)、日期 (visit_date)、 人流量 (people)。
请编写一个查询语句,找出人流量的高峰期。高峰期时,至少连续三行记录中的人流量不少于100。
官方解题使用3表相连,然后再增加筛选的3类条件。这种解法在真实应用中不可能使用,数据量大的情况下,执行会很慢,而且这里大量的判断逻辑应当放到业务层处理。
Mysql8.0可以使用窗口函数:因此可以根据规律这么写:
select id, visit_date, people from ( select id,visit_date,people,count(interval1) over(partition by interval1) as count1 from ( select id,visit_date,people,id-row_number() over(order by id) as interval1 from stadium where people>=100 ) t ) t1 where t1.count1 >=3
解析,分三步:
- 最内层使用窗口函数row_number()和id相减,发现规律,相邻的id的interval1的数字是一样的。
- 然后以interval1分组
- 最后,选择连续3个相邻的。
- ⚠️这是利用了id是完全相邻的特点,真实应用中不太可能发生。
585. 2016年的投资 (再看concat和group by)
sql架构:
CREATE TABLE IF NOT EXISTS insurance (PID INTEGER(11), TIV_2015 NUMERIC(15,2), TIV_2016 NUMERIC(15,2), LAT NUMERIC(5,2), LON NUMERIC(5,2) ) Truncate table insurance insert into insurance (PID, TIV_2015, TIV_2016, LAT, LON) values ('1', '10', '5', '10', '10') insert into insurance (PID, TIV_2015, TIV_2016, LAT, LON) values ('2', '20', '20', '20', '20') insert into insurance (PID, TIV_2015, TIV_2016, LAT, LON) values ('3', '10', '30', '20', '20') insert into insurance (PID, TIV_2015, TIV_2016, LAT, LON) values ('4', '10', '40', '40', '40')
问题:
写一个查询语句,将 2016 年 (TIV_2016) 所有成功投资的金额加起来,保留 2 位小数。
对于一个投保人,他在 2016 年成功投资的条件是:
- 他在 2015 年的投保额 (TIV_2015) 至少跟一个其他投保人在 2015 年的投保额相同。
- 他所在的城市必须与其他投保人都不同(也就是说维度和经度不能跟其他任何一个投保人完全相同)。
提示:
PID 字段是投保人的投保编号, TIV_2015 是该投保人在2015年的总投保金额, TIV_2016 是该投保人在2016年的投保金额, LAT 是投保人所在城市的维度, LON 是投保人所在城市的经度。
就如最后一个投保人,第一个投保人同时满足两个条件:
1. 他在 2015 年的投保金额 TIV_2015 为 '10' ,与第三个和第四个投保人在 2015 年的投保金额相同。
2. 他所在城市的经纬度是独一无二的。
第二个投保人两个条件都不满足。他在 2015 年的投资 TIV_2015 与其他任何投保人都不相同。
且他所在城市的经纬度与第三个投保人相同。基于同样的原因,第三个投保人投资失败。
所以返回的结果是第一个投保人和最后一个投保人的 TIV_2016 之和,结果是 45 。
方法1:
使用group by + having来按照条件筛选。
根据第一个条件,找到符合的TIV_2015:
select TIV_2015 from insurance group by TIV_2015 having count(1) >= 2
根据第二个条件,找到唯一的经纬坐标,此时可以使用concat(lat, lon)
这是因为concat组成一个新的字符串。
另外⚠️group by除了按照列来分组,还可以按照expression表达式分组。所以:
SELECT CONCAT(LAT, LON) FROM insurance GROUP BY LAT , LON HAVING COUNT(*) = 1
但是合并的时候:
select sum(insurance.TIV_2016) as TIV_2016 from insurance where TIV_2015 in ( select TIV_2015 from insurance group by TIV_2015 having count(1) >= 2 ) and concat(lat, lon) in (select concat(lat,lon) from insurance group by concat(lat, lon) having count(1) = 1 )
报告❌。
Expression #1 of HAVING clause is not in GROUP BY clause and contains nonaggregated column 'linshi.insurance.LAT' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
需要对上面黄色部分重写,去掉concat。不知道单独运行子句为何就可以
select sum(insurance.TIV_2016) as TIV_2016 from insurance where TIV_2015 in ( select TIV_2015 from insurance group by TIV_2015 having count(1) >= 2 ) and concat(lat, lon) in (select concat(lat,lon) from insurance group by lat, lon having count(1) = 1 )
其他方法:
比如使用inner join来筛选条件。也可以使用排除法用exist.