先问一行代表什么,再看SUM

让Claude修改统计SQL时,结果看起来合理却比手工账单大,常见原因是连接后的一行不再代表一张订单。先写明统计对象和包含条件,再检查连接关系;不是见到重复就加DISTINCT。

本文练习的需求很窄:只统计至少有一条明细的订单金额,每张订单算一次。订单金额以整数单位记录,样例只存在内存中,不连接任何业务库。

三张订单足以复现两个错误

虚构订单A金额100,有明细x、y;B金额100,有明细z;C金额80,没有明细。符合“至少一条明细”的只有A与B,人工期望合计200。若改为所有订单,答案才是280。

把orders与items按订单号直接JOIN后,A出现两行、B一行。SUM(o.amount)得到300,放大的不是金额字段,而是参与求和的行数。

金额去重不等于订单去重;订单A:100:两条明细,JOIN后出现两行;订单B:100:一条明细,与A金额相同;错误两种:直接SUM为300;DISTINCT为100;需求:有明细的订单:EXISTS保留订单粒度,总额200
本站原创示意图:金额去重不等于订单去重。虚构内存SQLite样例;C无明细,不计入本需求。查看大图 ↗

为什么SUM DISTINCT仍然不对

将表达式改成SUM(DISTINCT o.amount),本例只剩一个不同金额100,结果变成100。A与B是两张不同订单,恰好金额相同;按金额去重把本该保留的B也丢了。

正确的唯一性应来自订单标识,而不是金额或客户名称。给Claude追问时,应要求它列出连接后每一行和订单ID,不只输出修改后的SQL与一句“已去重”。

用EXISTS表达本例真实需求

本例只需要判断订单有没有明细,不需要从明细取字段。EXISTS子查询返回存在性,外层仍一行一订单,因此求和得到200。文末代码同时保留错误JOIN、错误DISTINCT、正确EXISTS和所有订单总和,方便对照。

若真实需求还要按商品、退款或日期拆分,EXISTS不一定就足够。先定义金额属于订单还是明细,必要时让明细先按订单聚合再连接,并明确没有明细时是否保留。不能把本文修复机械套入所有报表。

怎样运行和验收

将完整代码存为sql-join-grain.mjs,用Node.js 24 LTS运行:node sql-join-grain.mjs --demo。应得到bad为300、distinctWrong为100、correct为200、allOrders为280。Node可能显示内置SQLite实验性提示,不影响本例只读内存结果的解释。

请Claude解释四个数分别来自哪些行,再增加金额相同的第二订单、零明细订单两类回归样例。若只用每张订单金额都不同的样例,错误DISTINCT可能暂时蒙混过关。

把数据库方言和验证范围写进交接

代码已在本地Node.js 24的SQLite实际执行,并核对这四个数;没有用Claude真实生成查询,也没有在MySQL、PostgreSQL或线上数据中测试。换数据库时需核对语法、空值和执行计划。

正式替换查询前,在授权测试数据上比较逐订单明细与总数,保留旧查询及解释。不要为了得到期望总数删掉看似重复的真实订单。

参考资料与核验范围

资料核对日期:2026年10月20日。以下为官方原始资料;产品入口和可用范围仍以当前账户为准。

1. [官方资料1](https://platform.claude.com/docs/en/test-and-evaluate/develop-tests)

2. [官方资料2](https://www.sqlite.org/lang_select.html)