找出某只股票连续上涨日期最多的天数

问题描述

表 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 的记录,后续计算只针对这一只股票。比较用符号 "="。

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

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

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

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

Picture7png

编译生成 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