找出某只股票连续上涨日期最多的天数
问题描述
表 stock 记录多支股票的每日收盘价,字段为股票代码 CODE、交易日期 DT、收盘价 CL。现在针对指定的股票(本例为 100046),找出它历史上连续上涨期最长的那一段有多少天。这里说的连续上涨,指收盘价一天接一天不出现回落。
源数据
源表数据量较大,下表只列出部分记录:
CODE |
DT |
CL |
100046 |
2009-01-01 00:00:00 |
3.89 |
100046 |
2009-01-02 00:00:00 |
3.93 |
100046 |
2009-01-05 00:00:00 |
3.78 |
100046 |
2009-01-06 00:00:00 |
4.04 |
100046 |
2009-01-07 00:00:00 |
3.77 |
100046 |
2009-01-08 00:00:00 |
4.02 |
100046 |
2009-01-09 00:00:00 |
4.42 |
100046 |
2009-01-12 00:00:00 |
4.86 |
100046 |
2009-01-13 00:00:00 |
4.44 |
100046 |
2009-01-14 00:00:00 |
4.88 |
100046 |
2009-01-15 00:00:00 |
4.98 |
100046 |
2009-01-16 00:00:00 |
4.99 |
100046 |
2009-01-19 00:00:00 |
4.83 |
100046 |
2009-01-20 00:00:00 |
5.12 |
100046 |
2009-01-21 00:00:00 |
5.51 |
100046 |
2009-01-22 00:00:00 |
5.42 |
100046 |
2009-01-23 00:00:00 |
5.45 |
100046 |
2009-01-26 00:00:00 |
5.39 |
100046 |
2009-01-27 00:00:00 |
5.26 |
100046 |
2009-01-28 00:00:00 |
5.60 |
100046 |
2009-01-29 00:00:00 |
5.11 |
100046 |
2009-01-30 00:00:00 |
5.26 |
期望结果
只有一个值,即最长连续上涨天数:4
01-02 收盘价由 3.89 涨到 3.93,是连续上涨;01-05 由 3.93 跌到 3.78,上涨中断,新的连续上涨段从这里起步。
最长的一段是 2009-01-07 到 2009-01-12:起点 01-07 的 3.77 低于 01-06 的 4.04,随后 4.02、4.42、4.86 一路走高,四天没有回落。另外 2009-01-13 到 2009-01-16 也是 4 天(4.44、4.88、4.98、4.99),与它并列最长。
整只股票一共分出 9 段,各段天数是 2、2、4、4、3、2、1、2、2,最长 4 天且有两段并列,所以答案就是 4。
SQLazy 分步实现
连续上涨的本质是 "不回落的一段一段"。先把目标股票的数据按日期排好序,再用分段功能盯着收盘价:一旦收盘价回落到前一天的水平或以下,就说明上一段上涨结束了,组号加一,开始新的一段。这样每条记录都拿到了自己所属的连续上涨段号。接着按段号数一下行数,行数就是这段连续上涨的天数;最后不分组求一次最大值,就是整只股票最长连续上涨的天数。
下面逐一解释这些步骤。
Name |
Anchor |
Statement |
stock |
filter CODE = 100046 |
|
sort DT asc |
||
segment condition (CL <= CL[-1]); not_up; partition CODE; as NoRisingDays |
||
summarize count DT as ContinuousDays; group CODE, NoRisingDays |
||
summarize max ContinuousDays as max_ContinuousDays |
第 1 步:取出目标股票
filter CODE = 100046
从 stock 表筛出 CODE 等于 100046 的记录,后续计算只针对这一只股票。比较用符号 "="。

第 2 步:按日期升序排序
sort DT asc
分段是按记录顺序一段一段走的,顺序不对分出来的段就没有意义,所以先按交易日升序排好。

第 3 步:收盘价不涨就另起一段
segment condition (CL <= CL[-1]); not_up; partition CODE; as NoRisingDays
这是本题的核心一步。分段功能用条件式 CL <= CL[-1] 判断:当前收盘价不高于前一天时组号加一,高于前一天时组号不变。CL[-1] 是相对位置语法,取当前行的前一行,不用自己写自连接就能拿到前一天的价格。生成的组号写入新列 NoRisingDays,组号相同的记录就是一段连续上涨。
partition CODE 让每只股票各分各的段,互不干扰;本例已经筛成一只股票,这个分区是保留原解法的写法,以后放开到多只股票时也不用改代码。

第 4 步:数出每段有几天
summarize count DT as ContinuousDays; group CODE, NoRisingDays
按 CODE 和 NoRisingDays 分组,组内统计记录条数,聚合算法 count 写在被聚合项 DT 之前,结果命名为 ContinuousDays。这一步把记录级的长表压缩成段级的短表,每段一行。

第 5 步:取最长的一段
summarize max ContinuousDays as max_ContinuousDays
这时表里每行是一段的长度,题目只要最大值,所以不再分组,相当于把全表当成一个组求最大,直接得到最长连续上涨的天数。不写分组时结果只有一行,后续可以直接当单值使用。

编译生成 SQL
确认上述 5 步逻辑后,SQLazy 编译器自动生成原生 SQL(这里是 MySQL 语法):
WITH Value_2 AS (
SELECT CODE, DT, CL
FROM stock
WHERE CODE = 100046
)
SELECT MAX(ContinuousDays) AS max_ContinuousDays
FROM (
SELECT CODE, NoRisingDays, COUNT(DT) AS ContinuousDays
FROM (
SELECT CODE, DT, CL
, 1 + SUM(CASE
WHEN (CL <= col__2) THEN 1
ELSE 0
END) OVER (PARTITION BY CODE ORDER BY CASE
WHEN DT IS NULL THEN 1
ELSE 0
END, DT ASC ROWS UNBOUNDED PRECEDING) AS NoRisingDays
FROM (
SELECT Value_2.*, LAG(CL) OVER (PARTITION BY CODE ORDER BY CASE
WHEN DT IS NULL THEN 1
ELSE 0
END, DT ASC) AS col__2
FROM Value_2
) sub__3
) t_4
GROUP BY CODE, NoRisingDays
) t_5
SQLazy 让你用业务语言描述逻辑,而不是用 SQL 语法写嵌套查询。所谓 "连续上涨",本质就是 "价格不回落的一段一段"。SQLazy 用一句 segment 直接表达这个业务判断,而手写 SQL 要先构造前一天的价格、再累计出段号、再按段计数、最后取最大,四层嵌套才得出一个数字。
分段功能的判断是用条件式表达的,条件里可以写相对位置(前一天的价格)与聚合计算,不需要自己拼窗口函数,也不用先做自连接再比较。相较之下,SQL 里同一件事要包装成嵌套子查询,读代码的人得先认出 LAG 加 SUM 加 GROUP BY 这套组合,才能明白它在做 "分段计数"。
分步计算是这里的核心优势。取数、排序、分段、计数、求最大各占一句,每一步都能单独查看中间结果:第 3 步之后能直接看到每段一行的段表,最长的是哪一段一目了然;第 4 步之后能看到每段天数,第 5 步才收成一个值。若要在 SQL 里验证同样的中间结果,得把整条语句拆开重跑,出错时也很难定位是哪一层不对。
先算记录、再算值,也是 SQLazy 常见的写法:分段把记录归组,统计把组压成 "一段一行",最后不分组求最大收成单值。三步的粒度逐步变粗,与人的分析习惯一致。
官方链接
SQLazy 在线体验:sqlazy.com(免费,无需注册)
SQLazy 项目仓库:github.com/SPLWare/SQLazy

英文版 https://c.esproc.com/article/1794395827488