PAYATHON 2026

PostgreSQL 如何 update 未完成账单的 STATUS

支付小周

结论

先按 "ORDERID" 汇总付款记录,再比较付款总额和订单应付金额。如果订单已有付款记录,但累计付款金额仍小于订单金额,就把 "STATUS" 更新为 'in progress':

UPDATE "Schema"."ORDERS" AS o
SET "STATUS" = 'in progress'
FROM (
    SELECT
        s."ORDERID",
        SUM(s."SUMMARY") AS paid_total
    FROM "Schema"."SALES" AS s
    GROUP BY s."ORDERID"
) AS p
WHERE p."ORDERID" = o."ORDERID"
  AND p.paid_total < o."SUMMARY";

这段 SQL 基于以下假设:

  • "ORDERS"."SUMMARY" 是订单应付总额;
  • "SALES"."SUMMARY" 是每次实际付款的金额;
  • 订单至少有一条 "SALES" 记录,才表示已经发生过付款。

原查询为什么会更新错误

原查询中的两个条件没有正确关联:

o."ORDERID" IN (...)
AND s."SUMMARY" <> o."SUMMARY"

第一个条件只能说明订单存在付款记录。第二个条件比较的是单笔付款金额和订单总额,而不是该订单的累计付款金额。

例如,某个总额为 1000 的订单分两次付款:

第一次:400
第二次:600
累计:1000

这个订单已经付清,但两笔付款都不等于 1000,所以仍可能满足:

s."SUMMARY" <> o."SUMMARY"

另外,外层 "SALES" AS s 没有通过 "ORDERID" 与 "ORDERS" AS o 连接,会产生不必要的数据组合,也可能造成错误更新。

使用相关子查询的写法

也可以使用 EXISTS 来表达“已有付款记录,但累计金额不足”:

UPDATE "Schema"."ORDERS" AS o
SET "STATUS" = 'in progress'
WHERE EXISTS (
    SELECT 1
    FROM "Schema"."SALES" AS s
    WHERE s."ORDERID" = o."ORDERID"
    GROUP BY s."ORDERID"
    HAVING SUM(s."SUMMARY") < o."SUMMARY"
);

这条 SQL 会:

  1. 查找当前订单对应的付款记录;
  2. 按订单汇总付款金额;
  3. 只在累计付款金额小于订单应付金额时更新状态。

因为 EXISTS 要求子查询至少返回一个分组,所以没有任何付款记录的订单不会被更新。

避免重复更新

如果只想处理状态尚未变为 'in progress' 的订单,可以再加一个条件:

UPDATE "Schema"."ORDERS" AS o
SET "STATUS" = 'in progress'
WHERE o."STATUS" IS DISTINCT FROM 'in progress'
  AND EXISTS (
      SELECT 1
      FROM "Schema"."SALES" AS s
      WHERE s."ORDERID" = o."ORDERID"
      GROUP BY s."ORDERID"
      HAVING SUM(s."SUMMARY") < o."SUMMARY"
  );

使用 IS DISTINCT FROM 时,即使 "STATUS" 为 NULL,也能得到正确的比较结果。

注意事项

  • 金额字段应使用 numeric 或 decimal,不建议使用浮点类型,以免累计金额的直接比较受到精度影响。
  • 如果 "SALES"."SUMMARY" 允许为 NULL,SUM() 会忽略这些记录。如果所有记录都是 NULL,汇总结果也是 NULL,因此不会满足 < 条件。
  • 如果退款、冲正或负数付款也保存在 "SALES" 中,需要确认直接使用 SUM() 是否符合业务规则。
  • 如果订单可能被超额支付,使用 < 可以避免把已付清或超额支付的订单标记为 'in progress'。
  • 建议为 "SALES"("ORDERID") 建立索引,以提高按订单汇总付款记录时的查询效率。
  • 最好把付款写入和状态更新放在同一个事务中,避免并发付款时,订单状态与实际累计金额短暂不一致。

备注:内容仅供参考。