SQLazy:分组累计求和

问题描述:只保留开票行,金额累计从上次开票开始

有一张业务流水表(包含 ID、Date、Invoiced 和 Amount 四个字段)。每个月有一条记录,其中 Invoiced=1 表示该月开了发票。

现在要输出:每个 ID 下所有开票月份 ,并且每张发票的金额等于自上个月开票(或开始)到当前月所有金额的总和(包括当前发票本身)。

源数据:

ID Date Invoiced Amount
AAA 2023-01 0 10
AAA 2023-02 0 15
AAA 2023-03 1 15
AAA 2023-04 0 10
AAA 2023-05 0 10
AAA 2023-06 1 10
BBB 2022-05 0 40
BBB 2022-06 1 20
BBB 2022-07 0 30
BBB 2022-08 1 30

期望结果:只保留开票行,每行 Amount 是自上次开票以来的累计值。

ID Date Invoiced Amount
AAA 2023-03 1 40
AAA 2023-06 1 30
BBB 2022-06 1 60
BBB 2022-08 1 60

以 AAA 为例:

  • 第一张发票 2023-03:之前有 1 月 (10) + 2 月 (15) + 自身 (15) = 40

  • 第二张发票 2023-06:上次发票之后有 4 月 (10) + 5 月 (10) + 自身 (10) = 30

BBB 同理。

SQLazy 分步实现

|-----------|------------|-----------------------------------------------------------------------------------|
| Value | Anchor | Statement |
| t1 | invoice | sort id,dt desc |
| t2 | | compute invoiced cum as grp partition id |
| t3 | | summarize dt max as dt invoiced max as invoiced amount sum as amount group id grp |
| t4 | | derive id dt invoiced amount |

下面分别解释一下这些步骤。

第 1 步:按 ID 和日期排序,日期降序

sort id,dt desc

这一步是为了后续分组做准备。降序排列后,一个发票行与它前面的非开票行会先被处理,累计求和时这些行会被归入同一组。

第 2 步:对 invoiced 做累计求和,生成分组号

compute invoiced cum as grp partition id

在每个 ID 分区内,按当前顺序(降序)对 invoiced 进行累计求和(包括当前行)。结果如下:

第 3 步:按 ID 和 grp 分组汇总

summarize dt max as dt invoiced max as invoiced amount sum as amount group id grp

  • dt max:因为降序,每组内日期最大的是开票月份(发票行的日期)。

  • invoiced max:每组至少有一个 invoiced=1,所以最大值是 1。

  • amount sum:累加该组所有金额,即自该发票以来(包括自身)的总和。

第 4 步:选择需要的列

derive id dt invoiced amount

这一步只是清理输出,去掉辅助列 grp。

编译生成 SQL

完成上述步骤后,通过 SQLazy 的编译器可以生成等价的原生 SQL,无需手动编写。

这里生成 MySQL 语句:

复制代码
WITH t3 AS (
  SELECT
    id,
    grp,
    MAX(dt) AS dt,
    MAX(invoiced) AS invoiced,
    SUM(amount) AS amount
  FROM
    (
      SELECT
        id,
        dt,
        invoiced,
        amount,
        SUM(invoiced) OVER (
          PARTITION BY id
          ORDER BY
            CASE
              WHEN id IS NULL THEN 1
              ELSE 0
            END,
            id ASC,
            CASE
              WHEN dt IS NULL THEN 1
              ELSE 0
            END,
            dt DESC ROWS UNBOUNDED PRECEDING
        ) AS grp
      FROM
        invoice
    ) t2
  GROUP BY
    id,
    grp
)
SELECT
  id,
  dt,
  invoiced,
  amount
FROM
  t3
ORDER BY
  id,
  grp

你不需要读懂或调试这段 SQL,只需确认前面四步的逻辑正确,编译器就会输出可运行的代码。

列个表格总结一下:

|------|------------------------------------------------------|------------------|
| | 传统 SQL | SQLazy |
| 步骤数 | 多层 CTE + 窗口函数边界 | 4 步(排序→累计→汇总→选列) |
| 核心技巧 | 晦涩的 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING | 直观的"降序 + 累计求和" |
| 可读性 | 低,需要理解偏移窗口 | 高,步骤即思考过程 |
| 调试方式 | 手动拆解 CTE,反复运行 | 单步执行,每一步中间结果可见 |
| 跨数据库 | 需手动调整方言差异 | 编译器自动生成对应语法 |

这个实例展示了 SQLazy 处理"按事件重置的累计"类问题的自然性:通过一个简单的排序技巧加上累计求和,就能把复杂的分组逻辑拆解为清晰的动作。

相关推荐
Databend6 小时前
Databend 增量物化视图:基于 Change Tracking 的增量刷新与一致性读取
大数据·数据库·sql
疯狂打码的少年9 小时前
【数据库技术】SQL数据查询(SELECT基本语法)
数据库·笔记·sql·oracle
小白说大模型13 小时前
AI 提示词专栏:Zero-Shot 与 One-Shot Prompt 的差别与适用场景
大数据·数据库·人工智能·sql·深度学习·prompt
Sagittarius_A*13 小时前
【好靶场】SQL 注入-布尔盲注-2
sql·web安全·网络安全·sql注入
疯狂打码的少年14 小时前
【数据库技术】SQL高级查询(分组/排序/聚合函数)
java·数据库·笔记·sql
JMchen12315 小时前
【Android 性能优化实战 60 讲】03 Perfetto 现代分析利器:SQL 查询 Trace 数据,微秒级定位函数 CPU 耗时
android·sql·性能优化·实战·源码分析·perfetto·启动优化
harmony&15 小时前
MySQL 数据库从入门到实战:原理、安装与 SQL 详解
数据库·sql·mysql
程序员贺加贝15 小时前
报表大-IN-优化-一条-product_profile-超大-IN-SQL-背后的报表任务治理
java·spring boot·sql·性能优化·架构
2601_962065251 天前
[MySQL] SQL优化之性能分析
java·sql·mysql