Skip to content

贯穿案例:华东销售额同比

这是 V4 目标流程的教学走查,不是当前可运行接口。数值、字段和版本仅用于说明;真实实现与 Gold SQL 在各期开发时补齐。

用户问题:“2026 年上半年华东区销售额同比增长多少?” 用户是一位华东区域经理。

1. 先固定业务假设

教学假设:net_amount 已按经过审核的上半年净销售规则准备;不再额外连接退款表重复扣减。收入单位为人民币,日期按已确认的 order_date 口径,业务时区为 Asia/Shanghai。有效订单为 PAID 且非测试。真实生产口径必须由 V0/V3 的指标定义替换这些假设。

两段时间采用左闭右开:2026-01-01 至 2026-07-01,2025-01-01 至 2025-07-01。若业务要求退款按退款发生日计,则需要重做指标与计划,不能只替换字段名。

2. 理解与链接的输出

json
{
  "intent": "analytical_query",
  "domain_ids": ["sales", "store"],
  "metric_ids": ["net_sales"],
  "dimension_ids": [],
  "filters": [{"dimension_id": "region", "value": "华东"}],
  "current": ["2026-01-01", "2026-07-01"],
  "comparison": ["2025-01-01", "2025-07-01"],
  "operation": "year_over_year",
  "unresolved": []
}

Retrieval 先找可能相关的表列。Linking 再确认“销售额 → metric.net_sales”“华东 → region 字段和值”。Join Graph 补齐明细 → 订单 → 门店 → 城市 → 区域;这些中间表可能在文字召回中并不靠前。

授权先限制候选范围,计划中还要包含服务端确认的行权限。若用户改问华南,不能只把用户问题过滤改回华东后伪装成回答华南;应明确拒绝或说明范围。

3. 语义和计划

SemanticContext 带上已发布指标版本、表达式、强制过滤、日期角色和允许维度。IR 表达“分别算两期净销售额,再求差值比例”,不是把任意 SQL 当作计划。

text
period_sales(current) ─┐
                      ├─ delta = current - previous
period_sales(previous)┘  yoy = delta / previous(previous 非 0)

空值与零是不同状态:没有上年记录意味着没有比较基线;上年销售额恰好零意味着增长率分母为零。系统都应说明不可计算原因,而不是输出 0%。

4. SQL 形态示例

下面只是教学示例。:current_start 等是参数占位;业务实现应使用驱动参数绑定,禁止拼接用户值。已假设各维度键唯一,SCD2 需使用正确历史键。

sql
WITH period_sales AS (
  SELECT
    SUM(CASE
      WHEN o.order_date >= :current_start
       AND o.order_date < :current_end
      THEN i.net_amount END) AS current_sales,
    SUM(CASE
      WHEN o.order_date >= :previous_start
       AND o.order_date < :previous_end
      THEN i.net_amount END) AS previous_sales
  FROM fact_order_item i
  JOIN fact_order o ON o.id = i.order_id
  JOIN dim_store s ON s.id = o.store_id
  JOIN dim_city c ON c.id = s.city_id
  JOIN dim_region r ON r.id = c.region_id
  WHERE o.status = 'PAID'
    AND o.is_test = false
    AND r.id = :authorized_region_id
    AND (
      (o.order_date >= :current_start AND o.order_date < :current_end)
      OR
      (o.order_date >= :previous_start AND o.order_date < :previous_end)
    )
)
SELECT current_sales, previous_sales,
       (current_sales - previous_sales)
       / NULLIF(previous_sales, 0) AS yoy
FROM period_sales;

authorized_region_id 必须由服务端策略绑定;示例中的一个 WHERE 条件不能代替完整权限引擎、AST 血缘分析和数据库防线。金额采用 decimal 类型,避免整数除法和浮点金额误差。

5. 先校验,再执行,再验证结果

检查点检查内容失败出口
IR指标、时间角色、维度是否一致澄清或计划失败
AST单条只读、已知表列、安全函数、正确 Join拒绝
Policy区域范围、列血缘、权限版本拒绝,禁止修复
EXPLAIN估算成本、计划风险拒绝或要求缩小范围
Executor只读事务、超时、并发、行数分类错误或取消
Result Validator指标规则、粒度、NULL、重复警告或失败
Answer数值和结论是否有结果证据不输出无根据断言

普通语法/类型错误可以最多修复 2 次,每次重新过全链路;权限与安全错误不能修复。

6. 用最小夹具手算

假设华东有效明细 2026 上半年为 120、180 元,2025 上半年为 100、100 元;另有一笔华南 999 元、一笔测试单 500 元和一笔取消单 400 元。

期望:本期 300、上期 200、同比 50%。三个额外记录全部排除。再将上期改为 0,增长率应为空并说明不可计算;删除上期记录,则说明无比较数据。加入一条重复桥表关联时,必须由粒度校验或 Gold 对比发现翻倍。

7. 学会定位错误

症状先检查对应阶段
查到了库存表域与表召回V2
品牌 Apple 没过滤Value Linking 与字段绑定V2
金额翻倍关系基数与预聚合V2 / V4
取消单进入销售额指标强制规则V3 / V4
看见华南数据授权上下文、AST 与缓存 keyV4
SQL 正确但摘要胡说结果到文字的证据绑定V4
升级后此题退化逐题差异、版本与回归V6

这条案例对应多个模块,是阅读真实代码的主路线。下一步看 模块与契约

需求 → 代码 → 验证 → 复盘