AI会干活 / 免费教程
页面统计和后台导出对不上,怎样让 Codex 帮你追到数据源
页面看板显示 7 月订单 1286 笔,后台导出 CSV 却是 1289 笔,只差 3 笔,但业务方已经拿着数字来问了。你翻了一遍接口和 SQL,觉得哪都对,又说不清差异到底出在哪一环。
适合人群
负责报表或看板的开发者
先解决什么
页面统计值和后台导出结果差了几笔
学完结果
数据差异定位表和口径修正建议
你会学到什么
让 Codex 对比前端聚合、接口查询、SQL 条件和时区口径。
准备材料:页面截图、期望数字、接口响应、SQL 查询、样本数据。
交付物:数据差异定位表和口径修正建议
边界:聚焦数据口径与统计链路。
教程定位
这篇教程解决什么问题
页面看板显示 7 月订单 1286 笔,后台导出 CSV 却是 1289 笔,只差 3 笔,但业务方已经拿着数字来问了。你翻了一遍接口和 SQL,觉得哪都对,又说不清差异到底出在哪一环。
这篇教程给你一条可复现的排查路径:先把“差几笔”钉成能稳定复现的问题,再让 Codex 沿着“前端聚合 → 接口查询 → 数据服务 → SQL 条件”的链路逐层追,最后产出一张数据差异定位表。表里会标明每一环的当前口径、两边数字差在哪、证据是什么、应该由谁来改。你拿到的是定位结论和修正建议,而不是让 AI 猜一个原因后直接改生产查询。
读完这篇,你能把“页面 1286、导出 1289”这类问题,从一句模糊的抱怨变成一份可交接、可复核的排查记录。
使用场景
什么情况下最适合用这一套
你是维护报表、看板或后台统计列表的开发者。今天运营发来一条消息:“订单看板显示 7 月成交 1286 单,我自己导出来 1289 单,到底哪个是对的?”
这个场景有三个特征:
常见的真实原因包括:页面和导出用了不同的过滤条件(比如一个排除“已取消”、一个没排除);同一笔订单因 JOIN 产生重复行;时间范围边界差了一秒;前端把状态枚举映射错;后端按 UTC 存储但导出按本地时区切天;或者导出包含草稿/测试单,页面把它过滤掉了。
这类问题的共同点:不是代码“崩了”,而是两边对“一笔订单”的判定标准不一致。所以排查的重点不是找 bug,而是找口径差。
- 差异很小(几笔),所以不是数据全丢,更像是某类边界订单在两边走了不同口径。
- 页面和导出各有一套查询,前端还可能做二次聚合,链路上任何一个环节都能造成偏差。
- 业务方要的是“哪个数可信、差在哪几笔”,不是一篇代码讲解。
材料准备
开始前先把材料和边界备齐
开始之前,把下面这些材料准备好。材料越完整,Codex 越不容易靠猜:
如果你手里的数据量很大,先不要整库贴给 AI。抽一小段能代表差异的样本即可,比如:
如果数据涉及真实客户隐私,先用测试环境或脱敏数据复现。排查口径问题不需要真实姓名和手机号。
- 页面截图或链接,以及当时选择的筛选条件(日期范围、渠道、订单状态等)。
- 页面上显示的数字和来源 URL(哪个接口、哪些参数)。
- 后台导出文件的前几行和“期望数字”。
- 页面接口的真实响应样例(能脱敏就脱敏,但字段名、枚举值、数字不要改)。
- 导出用的 SQL 或导出逻辑的代码位置。
- 前后端仓库的访问方式,Codex 需要能读代码。
- 差异出现的稳定时间点,例如“每天都差 3 笔”还是“只在月底差”。
- 导出结果里多出来的那几笔订单号;
- 页面请求参数和接口返回的 `total` 字段;
- 对应日期边界的订单样例(比如 7 月 31 日 23:59 和 8 月 1 日 00:00 附近的订单)。
实操流程
按这套步骤把工作跑起来
【第一步:把差异钉死成可复现的描述】
不要带着“页面和导出对不上”这种描述去查代码。先回答四个问题:
把答案写成一两句话,例如:“看板选择 2026-07-01 至 2026-07-31、全部渠道、全部状态时,接口返回 total=1286;同条件导出 CSV 为 1289 行,多 3 行。”这就是之后给 Codex 的第一段上下文。
【第二步:让 Codex 画出从页面到数据库的统计链路】
打开你的代码仓库,让 Codex 找到这条数字从哪来。给它的任务不是“找 bug”,而是“画链路”:
让 Codex 输出一张“环节表”:每个环节对应哪个文件、哪个函数、做了什么、输入输出是什么。这张表会暴露一个常见事实:页面和导出常常根本没有共用同一个查询函数,只是看起来“都在查订单”。
【第三步:逐环节核对口径】
拿到链路表之后,对照下面这五类口径逐一核对,这是大多数差异的藏身之处:
让 Codex 对每个环节给出“页面口径”和“导出口径”两列,并标明差异可能在哪一环节。不要让它一次只报一个原因,要它把全部可疑点列出来,再按证据强弱排序。
【第四步:用最小样本验证差异】
AI 的结论必须能用数据验证。挑出差异最小的一小段时间(比如就查 7 月 1 日一天),分别跑页面接口和导出查询,把两边的订单号集合拿出来做差集:
这一步通常几行 SQL 就能完成,但价值很大:它把“可能的原因”收敛成“确定的 3 笔”。
【第五步:产出定位表和修正建议】
把结论整理成一张数据差异定位表,字段至少包括:
最后写清楚“哪个数是当前正确口径”以及“建议统一成什么口径”。修改代码前,先让负责该环节的人确认口径定义,再动手。
- 同一时刻、同一筛选条件下,页面数字和导出数字分别是多少?
- 差的是“多”还是“少”?差几笔?差的是金额还是笔数?
- 是不是每次都复现?换日期范围还差吗?
- 页面数字来自哪个接口请求?把请求 URL 和参数记下来。
- **过滤条件**:两边对“订单状态”的判定是否一致?是否都排除了已取消、已关闭、测试单?
- **去重键**:按订单号去重还是按订单行去重?JOIN 明细表后有没有产生重复行?
- **时间范围和时区**:页面传的 `2026-07-01` 到 `2026-07-31` 在代码里被解释成哪个时区的哪个时刻?数据库存的是 UTC 还是本地时间?`23:59:59` 还是 `24:00:00` 还是 `< 下一天 00:00:00`?
- **聚合单位**:按订单笔数还是按支付单、退款单、子订单数?金额是订单实付还是含运费、含优惠前?
- **状态枚举映射**:数据库里的值(如 `1/2/3`)和前端展示、导出筛选用的枚举(如 `paid/refunded`)是否一一对应?
- 页面上这个数字由哪个组件渲染;
- 它调用哪个 API;
- API 路由/service 里做了什么过滤、聚合、去重;
- 最终 SQL 或查询构造器的条件和 JOIN;
- 导出功能用的是哪段代码,和页面是否共用同一个查询。
- 导出有、页面没有的订单号:逐个看它满足了哪些条件,是哪一环把它漏掉/多算了;
- 页面有、导出没有的订单号:同样逐个看;
- 两边都有但金额不同的订单:看是不是聚合口径不同。
- 环节(页面聚合 / API 查询 / SQL 条件 / 时区处理);
- 代码位置(文件:函数或行号);
- 当前口径(两边各是什么);
- 差异笔数或金额;
- 证据(哪几笔订单、哪段代码、哪个请求参数);
- 建议修正(改哪边、怎么改、影响谁);
- 归属人(前端 / 后端 / 数据)。
输入示例
可以直接参考的输入材料
下面是一份比较完整的输入样例。你可以直接参考它的组织形式,把真实数据填进去。
注意:样例里的订单号和时间故意暴露出时区痕迹。真实排查时,Codex 会把这些时间换算成同一时区后判断是否落在日期范围内。
问题描述:
看板选择 2026-07-01 至 2026-07-31、全部渠道、全部状态时,页面显示成交订单 1286 笔;
同一条件后台导出 CSV 共 1289 行,多出 3 行。每天都会复现,差异笔数稳定为 3。
页面请求:
GET /api/orders/stats?start=2026-07-01&end=2026-07-31&channel=all&status=all
接口响应(脱敏后):
{
"total": 1286,
"gmv": 512340.00,
"start": "2026-07-01T00:00:00+08:00",
"end": "2026-07-31T23:59:59+08:00"
}
导出 SQL(export_orders.sql):
SELECT o.order_no, o.created_at, o.status, o.amount
FROM orders o
LEFT JOIN order_items i ON i.order_id = o.id
WHERE o.created_at >= '2026-07-01 00:00:00'
AND o.created_at <= '2026-07-31 23:59:59'
ORDER BY o.created_at;
导出文件多出来的订单号(脱敏):
SO202607010001(created_at=2026-06-30 16:30:00 UTC)
SO202607020088(created_at=2026-07-02 15:59:59 UTC)
SO202607310099(created_at=2026-07-31 16:01:00 UTC)
代码位置:
- 页面组件:web/src/pages/order-stats/index.tsx
- API 路由:api/src/routes/order-stats.ts
- 统计 service:api/src/services/orderStats.ts
- 导出模块:admin/src/export/orders.ts
- 订单表结构:db/schema/orders.sql提示词
可复制使用的提示词
把下面的提示词连同上面的输入样例一起发给 Codex。它要求 AI 输出定位表,而不是直接改代码。
如果仓库里代码很多,可以先只让 Codex 读统计链路相关文件;如果它需要搜索,允许它搜索后再回答。
我在排查一个报表数字对不上的问题,请按以下方式帮我,先不要修改任何代码:
1. 读一遍我提供的代码位置,画出“页面数字 → API → service → SQL/导出”的完整统计链路。
2. 对每个环节给出:文件位置、当前逻辑、输入输出。
3. 对照过滤条件、去重键、时间范围与时区、聚合单位、状态枚举五类口径,列出页面口径和导出口径。
4. 结合我提供的接口响应、SQL 和导出多出的订单号,判断差异最可能出现在哪一环节,并给出证据。
5. 输出一张“数据差异定位表”,包含:环节、代码位置、页面口径、导出口径、差异笔数、证据、建议修正、归属人。
6. 最后用一句话说明:当前哪个数字符合哪个口径,统一口径的建议是什么。
如果信息不足,直接列出你还需要哪些材料,不要猜测结论。输出样例
AI 应该输出到什么程度
用上面的提示词,Codex 可能输出类似下面的定位表(这是说明性样例,不是真实项目的结论):
这个样例展示的是“定位表”长什么样。真实输出里,每个结论都应该指向具体的代码行和订单号,方便你复核。
统计链路:
订单看板组件 web/src/pages/order-stats/index.tsx
→ GET /api/orders/stats
→ api/src/routes/order-stats.ts
→ api/src/services/orderStats.ts 的 countOrders()
→ SQL: SELECT COUNT(DISTINCT o.id) FROM orders o
WHERE o.created_at >= :start AND o.created_at < :end
导出模块 admin/src/export/orders.ts
→ SELECT o.order_no ... FROM orders o LEFT JOIN order_items i ...
WHERE o.created_at >= :start AND o.created_at <= :end
数据差异定位表:
| 环节 | 代码位置 | 页面口径 | 导出口径 | 差异 | 证据 | 建议修正 | 归属人 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 时间边界 | orderStats.ts / orders.ts | created_at >= start 且 < end | created_at >= start 且 <= end | 23:59:59 边界 1 笔被多算 | SO202607010001 | 导出改为 < end,或统一为“当天 00:00 至次日 00:00” | 后端 |
| 时区 | schema/orders.sql | 接口按 +08:00 转本地天 | 导出 SQL 按 UTC 原值切天 | 2 笔 | SO202607020088、SO202607310099 | 统一在 SQL 中按业务时区转换后过滤 | 数据/后端 |
| 去重 | orders.ts LEFT JOIN order_items | COUNT(DISTINCT o.id) | 直接 COUNT 行数 | 0(已排除) | 无 | 无需修改 | - |
当前判断:
接口 1286 与导出 1289 的 3 笔差异全部来自时间边界与时区口径,不涉及订单状态过滤。
建议统一口径为:所有统计查询使用业务时区 Asia/Shanghai,日期范围统一为 [start 00:00:00, end+1 00:00:00)。人工验收
人要怎么检查和改到可用
AI 能帮你把链路和可疑点列全,但“哪个口径才是业务想要的”必须由人确认:
不要因为 AI 输出看起来很完整就跳过人工复核。定位表只是排查记录,不是修改授权。
- **核对证据**:定位表里每一行差异,都要能在数据里复现。随便挑一笔“多出来的订单”,按表里的口径手工算一遍,确认它确实差在这里。
- **确认业务口径**:问业务方或产品:统计口径是“创建时间”还是“支付时间”?日期按哪个时区?订单取消后是否还计入?这些不是技术问题,AI 无权替你决定。
- **先改查询,不先改数据**:如果发现历史数据本身有问题(比如测试单混入),先确认清理流程,不要直接 DELETE。
- **改动前留回归样本**:把当前差异的 3 笔订单号存下来。修正代码后,用同一条件再跑一遍,确认 3 笔差异消失,且两边数字一致。
- **更新口径文档**:把最终口径(时间范围写法、时区、去重键、状态过滤)写进报表模块的注释或文档,避免下个月再吵一次。
失败反例
这些失败反例要提前避开
- **反例 1:只看总数,不看明细。** 页面 1286、导出 1289,直接拿两个总数让 AI 找原因。没有订单号集合做差集,AI 只能猜,最后给出“可能有时区问题”这类无法验证的结论。正确做法是先抽出多出来的 3 笔订单号。
- **反例 2:让 AI 直接改 SQL,而不是先定位。** 提示词写“帮我修一下,让导出和页面一致”。AI 可能直接把 `<=` 改成 `<`,但真正的原因是时区,改完数字还是对不上,还动了一个原本正确的查询。定位和修改要分成两步。
- **反例 3:忽略时区和日期边界。** 只比 `created_at` 的日期字符串,没注意数据库存的是 UTC、页面按 +08:00 切天。结果是“差几笔”在月初或月底特别明显,但平时看起来没问题,问题被长期掩盖。
- **反例 4:把 JOIN 重复行当成数据问题。** 导出 SQL 里 `LEFT JOIN order_items` 让一单多行,直接数行数导致偏多,却以为是订单表里有脏数据。应该先看是不是查询本身没去重。
- **反例 5:不复现就贴代码让 AI 猜。** 没有固定页面条件、接口参数和导出文件,只把一堆代码丢给 AI。AI 能画出链路,但无法告诉你差异在哪几笔,最后给的是“可能性列表”而不是定位结论。
主题边界
它和相邻主题的区别
这篇文章解决的是“同一统计链路里,页面和导出的数字对不上”,核心是口径与数据源定位。
本文不解决:生产数据本身的修复、口径标准的最终决策、以及涉及金额重算的财务修正。这些都需要业务负责人确认后再执行。
- 与“接口偶发 500 的排查”不同:那篇处理的是请求失败、日志和报错链路,这篇处理的是请求成功但数字不一致。
- 与“从代码里挖业务规则”不同:那篇是从现有代码反推规则和影响范围,这篇是以一个确定的数字差异为起点,沿链路找口径差。
- 与“改表单字段前追数据流”不同:那篇发生在动手改功能之前,目的是搞清字段从 UI 到数据库的流向;这篇发生在数字已经对不上的事后排查,产出一张定位表而不是修改方案。
可直接套用的流程
1. 先写清楚任务目标:这次要让 AI 帮你完成什么工作,而不是泛泛地问一个问题。
2. 再给资料边界:哪些背景、数据、约束、口径必须被使用,哪些内容不能编。
3. 最后规定输出格式:用清单、表格、方案、话术还是复盘报告,并保留人工检查。