跳到主要内容

明细表有行、汇总查无此条?跨 SQL 判定集粒度分叉

· 阅读需 6 分钟

排查一个数据看板线索:某广告 offer 在「诊断明细」里被判疑似停投、并附了再投资建议,点进「建议投放」清单却查无此 offer——明细页指着一张清单,清单里没有这行。

在开发 AI运营 时遇到此问题——基于大语言模型的智能分析平台,自动洞察市场趋势、用户行为与销售数据;诊断明细与建议清单分别来自管道里两个相邻查询的产出。

TL;DR

两个查询对同一个业务谓词(「疑似停投」)用了不同的聚合粒度:明细查询按 计划×offer 粒度判定——任一计划末周无消耗即判疑似;候选清单按 offer 整体粒度判定——挂着的全部计划末周均无消耗才入选。某 offer 在其中一个计划末周仍有消耗,于是「明细判疑似、汇总不入选」,呈现层指向的明细悬空。修法三件套:口径统一收紧到整体粒度、两侧判定 CTE 逐字同源、引用对方行集前校验包含关系

问题现象

两个查询各自定义「疑似停投」:

-- 查询④ 诊断明细:pair 粒度(计划 × offer)
WITH consumption_paused_q4 AS (
SELECT offer_id, campaign_id
FROM spend_weekly
GROUP BY offer_id, campaign_id -- 每个计划单独看
HAVING SUM(spend) FILTER (WHERE is_last_week) = 0
)

-- 查询⑤ 建议投放候选:offer 粒度(跨全部计划汇总)
WITH suspected_paused_q5 AS (
SELECT offer_id
FROM spend_weekly
GROUP BY offer_id -- 整个 offer 一起看
HAVING SUM(spend) FILTER (WHERE is_last_week) = 0
)

出问题的 offer 挂在多个计划下,其中一个计划末周仍有消耗(3.09):

查询④(pair 粒度):  计划A 末周消耗 0     → 判疑似 ✓,并给出建议
查询⑤(offer 粒度): 跨计划汇总末周消耗 3.09 → 不入选 ✗
呈现层:明细页说「见建议投放表」,建议投放表里没有它

任务不报错、行数对得上大数,悬空只有顺着某条明细点过去才会撞见。

根因

「同源」契约有覆盖盲区。管道规范要求相邻查询的特征列 CTE 逐字同源——这条契约被严格遵守了;但它只约束特征列,判定集(WHERE 之前的那个业务谓词)不在契约范围内。「疑似停投」在两个查询里被独立实现了两次,粒度不同:pair 级判定对「单个计划内无消耗」敏感,offer 级判定对「所有计划都无消耗」敏感。同一个 offer 两种结论,数学上必然存在交叉带。

跨查询引用把分叉放大成了悬空:呈现层拿查询④的行当明细、查询⑤的表做入口,却没人校验过「④的行集 ⊆ ⑤的行集」。聚合口径不一致是数据仓库最经典的一致性陷阱之一——此前写过的 SUM 比率列导致聚合后指标暴涨是它的另一种形态:聚合发生在了错误的层级上。

解决方案

步骤 1:先定口径,再写 SQL

业务问题只有一个答案:「这个 offer 还投不投」是 offer 级决策,判定就该收紧到整体粒度——挂着的全部计划末周均无消耗、且无任何运营标注行,才判疑似。口径变更升 rule_version,可追溯。

步骤 2:判定集 CTE 逐字同源

把判定 CTE 抽成同一段文本,两个查询直接引用;契约同步升级:逐字同源覆盖判定集(含粒度),不止特征列。从此同一谓词只有一处定义,改口径只改一处。

步骤 3:跨查询引用前,校验行集包含关系

把「标记集 ⊆ 对方行集」做成固定校验(可入库为测试):

-- 悬空检测:④ 判了疑似、⑤ 却查无此行
SELECT q4.offer_id
FROM consumption_paused_q4 q4
LEFT JOIN suspected_paused_q5 q5 USING (offer_id)
WHERE q5.offer_id IS NULL;

这条查询返回 0 行,呈现层才有资格把两个产出拼在同一张页面上。

步骤 4:重放验证后上线

新口径对历史快照重放:逐行核对翻转方向(疑似↔正常)全部正确、零误伤、零悬空,再重跑生产验证通过,才完成收口。

注意事项

  • 「同源」契约的覆盖面必须包含判定集粒度,特征列同源救不了谓词两处定义。
  • 同一业务谓词(判停投、判爆款、判流失…)全管道只允许一处定义;发现第二处实现即是事故预备役。
  • 新增跨查询/跨模块引用(A 的输出行指向 B 的产出表)前,先跑行集包含校验(标记集 ⊆ 对方行集),不要等用户点到悬空链接。
  • 口径变更必须升版本并对历史数据重放,只看「新数据跑通」会漏掉存量结论的翻转风险。

常见问题

为什么两条 SQL 对同一份数据给出不同结果?

常见三个差异:聚合粒度(GROUP BY 维度不同——本例的计划×offer 与 offer 整体)、过滤口径(WHERE/HAVING 条件不同)、取数时点(查询时间不同)。粒度分叉最隐蔽:两条 SQL 各自都对,结论却可以相反。

SQL 的 HAVING 和 WHERE 在聚合判定里怎么选?

WHERE 在分组前过滤行,HAVING 在分组后过滤组。但选对关键字之前先选对粒度——「以什么为一组」决定了判定的灵敏度:粒度越细越容易命中(单组满足即判),越粗越保守(全部满足才判)。两条 SQL 粒度不同,判定结论就可能相反。

如何避免报表之间的数据口径不一致?

同一业务谓词只在一处定义,判定 CTE 多查询逐字同源;跨查询引用对方行集前跑包含关系校验(A ⊆ B);口径变更升版本号并对历史数据重放验证。一致性不靠约定俗成,靠契约加校验。

CCLEE

独立开发者,24年电商行业实战经验,专注将AI能力落地于真实商业场景。

合作咨询