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 处理“按事件重置的累计”类问题的自然性:通过一个简单的排序技巧加上累计求和,就能把复杂的分组逻辑拆解为清晰的动作。

SQLazy 在线体验sqlazy.com(免费,无需注册)
SQLazy项目仓库github.com/SPLWare/SQLazy