题目
表:Logs
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| num | varchar |
+-------------+---------+
在 SQL 中,id 是该表的主键。
id 是一个自增列。
找出所有至少连续出现三次的数字。
返回的结果表中的数据可以按 任意顺序 排列。
结果格式如下面的例子所示:
示例 1:
输入:
Logs 表:
+----+-----+
| id | num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+----+-----+
输出:
Result 表:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
解释: 1 是唯一连续出现至少三次的数字。
思路
解法一: 判断(id+1, num) and (id+2, num)是否在表中
当遍历到每一行的时候,判断(id+1, num)和(id+2, num)是否在表中,在的话,就select,可能存在大于3条的会有重复数据,所以还需要去重一次。
优点: 思路清晰明了,方便理解。
缺点: 如果题目要求判断连续出现10次或者100次,对于sql的编写会异常庞大和不便。
select distinc num as ConsecutiveNums
from
Logs
where
(id+1, num) in (select * from Logs)
and
(id+2, num) in (select * from Logs);
注意: 在判断(id+1, num)和(id+2,num)时,应该在对logs表的数据线进行查询,然后进行in查询,而不是直接写Logs。
解法二: 2次JOIN连表
将同一张表通过2次连接,形成3张表,l1\l2\l3,其中连接条件为 l1\l2\l3的ID保持连续,不妨设为递减。然后查询出l1.num=l2.num=l3.num的li.num值,最后对num去重即可。
优点: 思路简单,连表方便理解。
缺点: 和解法一一样拓展性差,过多连表的话也可能性能有一定下降。
select distinct l1.num as ConsecutiveNums
from logs l1
left join logs l2 on l1.id = l2.id + 1
left join logs l3 on l2.id = l3.id + 1
where l1.num = l2.num and l2.num = l3.num;
解法三: 变量+case when判断
梳理题目条件:1、id必须连续 2、num必须相等 3 、count(满足1和2的行数)>=3 不难分析得出,在进行SQL查询的时候,需要判断:
- 条件a: 上一行ID,不妨设为preId 和 当前ID是否满足 preId+1 == Id;
- 条件b: 上一行Num,不妨设为preNum 和 当前Num是否满足 preNum == Num
当且仅当(a is True) && (b is True)时,计数++
那么当条件a和条件b都不满足或者说部分满足的情况呢?
不论是a,b均不满足,或者是部分不满足的情况,说明当前这行已经是新一行了,应该作为新的判断标准,更新preId 和 preNum 的值,且该行第一次出现,所以count初始化为1。
如何保存count的值用于后续判断呢?
可以新增一列,统计第i行在[1,i]行中出现的连续次数,记为count.
解决方案
- 设置3个变量
- 判断变量 preId+1 == Id and preNum == Num
- 如果条件满足,count++
- 如果不满足,count=1
- 更新 preId 的值,更新preNum的值
在了解了sql变量语法后,sql如下:
select distinct num as ConsecutiveNums
from (
select @count := case
when @preId+1 = id and @preNum = num then @count := @count + 1
else @count := 1
end as CNT,
@preId := id,
@preNum := num,
num
from (select * from logs order by id) as t1,
-- 声明变量
(select @preId := null, @preNum := null, @count := null) as t2
) as temp
where temp.CNT >= 3;
注意:之前参照一位大佬的解答,一直想加入一个@preId变量在case when里,还想再then中完成更新,没做出来,花费很多时间,后面发现这样写其实更清楚明了,每一行新增@count变量计算连续值,然后更新@preId和@preNum。
优点: 对于问题的扩展性很好,可以延伸到连续出现N次、不要求ID连续的连续出现次数(去除掉preId判断即可)
缺点: 需要了解sql的变量声明 赋值 case when,已经语句之间的连接和查询,较为复杂。