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 会:
- 查找当前订单对应的付款记录;
- 按订单汇总付款金额;
- 只在累计付款金额小于订单应付金额时更新状态。
因为 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")建立索引,以提高按订单汇总付款记录时的查询效率。 - 最好把付款写入和状态更新放在同一个事务中,避免并发付款时,订单状态与实际累计金额短暂不一致。
备注:内容仅供参考。
