LeetCode SQL题咨询:校验单调递增区间查询的正确性及简化方法
现有SQL代码的问题排查
你写的这段代码无法得到正确结果,其实你一开始想的「拆分段起点、段终点再做匹配」的思路本身是成立的,只是从语法到逻辑存在多层实现疏漏:
- 基础语法错误
from table numbers写法不符合SQL规范,FROM后直接跟表名即可,不需要额外加table关键字with x as定义CTE时,后接的查询块没有用圆括号包裹,多数数据库会直接抛出语法错误- 自关联时列名不匹配:两个子查询给行号起的别名都是
rownum,但JOIN条件写的是t1.numrow = t2.numrow,不存在numrow这个列,会直接报找不到列的错误
- 逻辑层面的核心缺陷
- 所有窗口计算(
lag取前置值、lead取后置值、row_number编号)都没有绑定可靠的排序规则:SQL表本身是无序的集合,不指定明确的排序逻辑,算出来的前后行关系完全随机,根本没法匹配连续递增的序列 - 边界值处理失效
- 第一行的前置值被默认设为0,只要第一个id大于0就会被判定为段起点,但第一行本身没有前置参考值,这个判断没有实际意义
- 最后一行的后置值被默认设为0,如果表末尾刚好是连续的递增段,代码根本抓不到这个段的结束点,直接漏输出最后一个区间
- 起点和终点的匹配逻辑不可靠:用
ORDER BY (SELECT NULL)生成的随机行号,没法保证同一段的起点和终点是一一对应的,很容易出现错配的区间
- 所有窗口计算(
按照你给的样例数据[2,3,5,1,4,2,7,8],现有代码跑出来的结果会是错位、缺漏的,完全达不到题目要求的输出效果。
更简洁的实现方案
刚好你已经熟悉select、from、where、group by的基础语法,用通用的「连续序列分段法」就可以实现,不需要嵌套多层子查询,也不用拆分起点、终点做自连接,核心逻辑和你已经掌握的分组思路完全匹配:
- 逐行判断当前行是不是新的递增段的起点:如果当前id比上一行的id小(或者是表的第一行),就给一个新的分组编号
- 所有在同一个分组内的id,都属于同一段连续递增序列
- 按分组号做聚合,取每组的最小id、最大id,拼出来就是对应的区间
对应的代码如下:
WITH row_with_prev AS ( SELECT id, -- 取上一行的id,没有前置值时给一个足够小的数,保证第一行触发新分组 LAG(id, 1, -999999) OVER(ORDER BY (SELECT NULL)) AS prev_id FROM numbers ), group_marked AS ( SELECT id, -- 累计计算分组号:遇到比上一行小的id就+1,否则和上一行同组 SUM( CASE WHEN id <= prev_id THEN 1 ELSE 0 END ) OVER(ORDER BY (SELECT NULL) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS group_no FROM row_with_prev ) SELECT CONCAT(MIN(id), '-', MAX(id)) AS increasing_range FROM group_marked GROUP BY group_no ORDER BY group_no;
这个方案的优势很明显:
- 没有多层嵌套的子查询,逻辑是顺着数据流一步步往下走的,容易调试
- 边界处理完整:第一行、最后一行的场景都能覆盖,不会漏算区间
- 最后一步的分组聚合完全是你已经熟悉的
group by语法,学习成本很低
补充说明:LeetCode的测试环境中,表的记录顺序是固定的,用
ORDER BY (SELECT NULL)可以稳定复现题目要求的顺序;但在实际生产环境中,一定要给表增加固定的排序字段(比如自增序号、插入时间戳),不要依赖随机排序,不然结果会不稳定。
内容的提问来源于stack exchange,提问作者Duno
相关产品推荐
相关产品推荐

