5 步搞定 4 层嵌套 SQL 才能算清的股票连涨天数

嵌套 4 层的 SQL

“最长连续上涨天数”,这道题在数据面试里被反复用来考人,据说通过率不到 20%。不是因为它难,而是因为它把 SQL 的一个老毛病暴露得干干净净:你明明知道逻辑是什么,写出来却是一坨嵌套

先看需求。一张股票行情表 Stock,记录了每天的收盘价。要算某支股票历史上收盘价连续上涨的最长天数

逻辑很简单:按日期排序,依次判断每一天是否比前一天上涨,把连续上涨的区间标出来,数每个区间有多长,取最大值。

你可以在脑子里过一遍这个逻辑——排序、判断、分段、计数、取最大。五步,清晰得很。

但落到 SQL 里,它长这样:

select max(ContinuousDays)
from (
    select count(*) ContinuousDays
    from (
        select sum(UpDownTag) over (order by DT) NoRisingDays
        from (
            select DT,
                case when CL > lag(CL) over (order by DT) then 0 
                else 1 end UpDownTag
            from Stock
            where CODE = 100046
        )
    )
    group by NoRisingDays
)

4 层嵌套。从最内层往外读:

  • 第 4 层:case when CL > lag(CL) ... then 0 else 1 end——涨了记 0,没涨记 1。

  • 第 3 层:sum(UpDownTag) over (order by DT)——累加。涨了加 0,值不变;没涨加 1,值 +1。连续上涨的区间内,这个累加值保持不变。

  • 第 2 层:按这个累加值分组,count(*) 数每个区间有多少天。

  • 第 1 层:取最大值。

费了好大劲算是读懂了,似乎原理也明白了。但下次需求变了呢?

比如:“把‘上涨’改成‘涨幅超过 1% 才算涨’”——你得从最内层的 case when 开始改,然后确认改完之后第 3 层的累加逻辑还成立,再确认第 2 层的分组逻辑还成立,最后确认最外层的 max 没被影响。4 层嵌套,改一层就要在脑子里重跑四层。

再比如:“不仅要算最长连涨天数,还要知道最长连涨发生在什么时候”,最外层只有一个 max,拿不到起止日期。你得从最内层开始把日期字段一层层往外传,整个查询重写一遍。

典型的一看就会,一做就废。

这就是嵌套 SQL 的代价:每一层嵌套都是一道“这道题改完会不会影响上一层”的推理题。代码能跑,但改它的成本比重新写一遍还高。不是 SQL 的问题,是“把顺序逻辑塞进嵌套结构”这个动作本身的问题。

SQLazy 的自然写法

SQLazy 的做法很简单:别把逻辑塞进嵌套里,按顺序写出来。

最长连续上涨天数,用 SQLazy 的 workflow 写:

Name

Anchor

Statement

t1

stock

filter CODE = 100046

t2


sort DT asc

t3


segment CL down as NoRisingDays

t4


summarize DT count as ContinuousDays group NoRisingDays

t5


summarize ContinuousDays max as max_ContinuousDays

在线运行本例:https://www.sqlazy.com/?4GL

5 步,和你在脑子里想的逻辑顺序完全一致。

  • filter:筛选目标股票

  • sort:按日期排序

  • segment:按条件分段——CL down 的意思是“如果收盘价下跌,就新开一组”。连续上涨的区间自动落到同一组

  • summarize:数每组有多少天

  • summarize:取最大值

每一步都可以预览中间结果。第 3 步执行完,你立刻能看到 NoRisingDays 这一列,同一个数字代表同一个连续上涨区间。分段对不对,当场就知道。

..

单支股票算完了,下一个需求来了:找出所有股票中,出现过连涨超过 3 天区间的有哪些股票?

在 SQL 里,这需要在前面的嵌套外面再套一层 PARTITION BY CODE,然后在外层 GROUP BY CODE 再过滤。代码又厚了一层,可读性再降一档。你得从最内层的窗口函数开始理解,一路推导到最外层,才知道这个 HAVING 到底在过滤什么。

在 SQLazy 里,逻辑没有任何变化。把 partition CODE 加在需要按股票分组的操作上即可:

Name

Anchor

Statement

t1

stock

sort CODE asc, DT asc

t2


segment CL down as NoRisingDays partition CODE

t3


summarize DT count as ContinuousDays group CODE, NoRisingDays

t4


filter ContinuousDays > 3

t5


derive CODE distinct

在线运行本例:https://www.sqlazy.com/?49V

仍然是顺序读下来的 5 步:

  • sort:按股票代码和日期排序

  • segment partition CODE:对每支股票分别按下跌分段

  • summarize group CODE, NoRisingDays:统计每支股票每个连涨区间的天数

  • filter ContinuousDays > 3:只保留连涨超过 3 天的区间

  • derive CODE distinct:提取出现过这些区间的股票代码(去重)

每一步都看得懂。不需要在脑子里展开嵌套,不需要推理“这一层改了会不会影响上一层”。

编译器保证:SQL 自动生成 100% 准确

workflow 写完了,逻辑也确认了。接下来要做的只有一件事:点一下“编译”按钮。SQLazy 的编译器会把 workflow确定性地转换成目标数据库的原生 SQL。

..

注意,是“编译器”,不是“AI 生成器”。这两者有本质区别:


AI 生成器

SQLazy 编译器

输出稳定性

同样的输入,每次输出可能不同

同样的输入,永远输出同样的 SQL

准确性

概率性的,可能产生幻觉

确定性的,100% 准确

可审计性

无法追溯“为什么生成这段代码”

每一步转换都可追溯、可验证

编译器不做“猜测”。它只做一件事:按照固定的规则,把 workflow 的每一步翻译成对应的 SQL 语法。sort 就是 ORDER BY,filter 就是 WHERE,segment 就是窗口函数累加分段,summarize 就是 GROUP BY。一一对应,确定无疑。

这意味着:

  • 没有幻觉——编译器不会“发明”不存在的表或字段,不会“猜测”业务逻辑

  • 没有随机性——同样的 workflow,今天编译和明天编译,结果完全一样

  • 可审计——workflow 的每一步都能追溯到生成的 SQL 片段,审查者可以验证“为什么这段 SQL 是这样写的”

如果目标数据库从 MySQL 换成 PostgreSQL,只需切换一个选项,编译器自动适配 SQL 方言。workflow 不用改,因为逻辑没变,变的只是“翻译目标”。

..

workflow 是给人看的,SQL 是给数据库跑的。编译器保证两者永远一致。

LLM 辅助规范

在 SQLazy 的 workflow 中,AI 的角色也被重新定义了。以往做法中,AI 负责从自然语言直接生成最终 SQL,高难度、高幻觉率。而在 SQLazy 中,AI 只做一件事:把用户口语化的步骤描述转译成规范的 workflow 语法。

..

LLM 将自然输入规范成 SQLazy 语句

前者是“决策”,让 AI 决定 SQL 怎么写,风险极高。后者是“翻译”,让 AI 把口语转成规范格式,即便转译有偏差,你在 workflow 层面一眼就能发现,纠正成本几乎为零。

AI writes the logic. A compiler writes the SQL.