Files
Neo-ZQYY/docs/database/changes/2026-04-23__app2a_member_order_count.md
Neo 76a23639ee feat(db): app2a DWS 新列 + ai_cache CHECK 约束放开
1. db/etl_feiqiu/migrations/20260423__app2a_add_member_order_count.sql:
   - dws.dws_finance_area_daily 增加 member_order_count 列 (integer NOT NULL DEFAULT 0)
   - 重建 app.v_dws_finance_area_daily RLS 视图暴露新列
   - 同步重建 dws.v_dws_finance_area_daily(遵守双 schema 规则)
   - 列顺序因 PostgreSQL CREATE OR REPLACE VIEW 限制必须加在末尾

2. db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql:
   - biz.ai_cache.chk_ai_cache_type CHECK 约束放开 app2a_finance_area 新值
   - DROP 旧 7 项 CHECK + CREATE 含 8 项的新 CHECK(新增 app2a_finance_area)

3. docs/database/changes/ 两份变更文档:
   - 变更说明 + 兼容性 + 回滚策略 + 3-4 条验证 SQL

测试库已执行 + 验证通过。生产库待上线窗口按 checklist 跑。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:55:01 +08:00

8.7 KiB
Raw Blame History

数据库变更dws_finance_area_daily 增加 member_order_count 列

日期2026-04-23 迁移脚本:db/etl_feiqiu/migrations/20260423__app2a_add_member_order_count.sql 相关设计文档:docs/ai/app2_finance_multi_app_design.md · Phase B 涉及库:etl_feiqiu(六层 DWS 风险等级:(新增列带默认值,对历史行为无破坏)


1 · 变更说明

新增列

Schema 列名 类型 默认值 Nullable 说明
dws dws_finance_area_daily member_order_count integer 0 NOT NULL 会员订单数(区域粒度,从 DWD 聚合 is_member_order = true 的订单计数)

更新视图

Schema 视图 变更
app v_dws_finance_area_daily CREATE OR REPLACE · 暴露 member_order_count 列(追加到视图末尾)· RLS 过滤保持
dws v_dws_finance_area_daily CREATE OR REPLACE · 暴露 member_order_count 列(追加到视图末尾)· RLS 过滤保持 · 遵守双 schema 规则

列顺序说明PostgreSQL CREATE OR REPLACE VIEW 仅允许在视图末尾追加列,不能在中间插入。因此 member_order_count 放在 updated_at 之后(而非紧邻 order_count)。列顺序不影响语义,后端查询按列名访问。

新增文档

无(本文档即为同步文档)


2 · 兼容性影响

对 ETL飞球 Connector

  • 必须改造 · apps/etl/connectors/feiqiu/loaders/dws_finance_area_daily*.py(对应 loader
    • 聚合 DWD 订单数据时按 (area_code, stat_date) 分组
    • is_member_order = true 的订单数归入 member_order_count
    • 上线前 ETL 未改造时历史与新增数据均为 0(接受降级)

对后端 API

  • 新增 · 向后兼容 · 后端查询 app.v_dws_finance_area_daily 选取列时增加 member_order_count 即可访问
  • 已知消费者:apps/backend/app/ai/prompts/app2a_finance_area_prompt.py(新建,本设计文档 Phase C 实施)
  • 其他读 v_dws_finance_area_daily 的代码:未读新列则无影响

对小程序字段映射

  • 无直接影响(小程序不直接读 DWS 视图,通过后端 /api/board/finance 中转)
  • P2 上线后,面板的"区域会员订单占比"派生字段会生效(经 app2a prompt 内部计算,不通过 board API

对 FDW 跨库映射

  • etl_feiqiu.app.v_dws_finance_area_daily 通过 FDW 映射到 zqyy_app.etl_app schema若已映射需确认 FDW 侧是否要重导FDW foreign table 的列定义可能需同步)
  • 验证项zqyy_app 端查询 etl_app.v_dws_finance_area_daily 是否能看到新列,若不能,需重跑 FDW 刷新脚本 db/fdw/ 下对应 remap 脚本

3 · 回滚策略

回滚步骤(按顺序执行)

BEGIN;

-- 1. 先 DROP 两个 RLS 视图(否则 DROP COLUMN 会因依赖失败)
DROP VIEW IF EXISTS app.v_dws_finance_area_daily;
DROP VIEW IF EXISTS dws.v_dws_finance_area_daily;

-- 2. DROP 新列
ALTER TABLE dws.dws_finance_area_daily
  DROP COLUMN IF EXISTS member_order_count;

-- 3. 重建两个 RLS 视图(去掉 member_order_count · 从 db/etl_feiqiu/schemas/app.sql:714 复制原定义)
-- 注意:因 member_order_count 在原视图中是末尾追加,回滚时只需去掉该列即可
-- app schema
CREATE OR REPLACE VIEW app.v_dws_finance_area_daily AS
SELECT id, site_id, tenant_id, stat_date, area_code,
       table_fee_amount, goods_amount, assistant_pd_amount, assistant_cx_amount,
       gross_amount,
       discount_groupbuy, discount_vip, discount_manual,
       discount_gift_card, discount_rounding, discount_other, discount_total,
       confirmed_income,
       cash_pay_amount, cash_paper_amount, scan_pay_amount, groupbuy_pay_amount,
       recharge_cash_inflow, cash_inflow_total, cash_outflow_total, cash_balance_change,
       card_consume_total, recharge_card_consume, gift_card_consume,
       recharge_cash, first_recharge_cash, renewal_cash,
       order_count,
       created_at, updated_at
  FROM dws.dws_finance_area_daily
 WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;

-- dws schema双 schema 规则)
CREATE OR REPLACE VIEW dws.v_dws_finance_area_daily AS
SELECT id, site_id, tenant_id, stat_date, area_code,
       table_fee_amount, goods_amount, assistant_pd_amount, assistant_cx_amount,
       gross_amount,
       discount_groupbuy, discount_vip, discount_manual,
       discount_gift_card, discount_rounding, discount_other, discount_total,
       confirmed_income,
       cash_pay_amount, cash_paper_amount, scan_pay_amount, groupbuy_pay_amount,
       recharge_cash_inflow, cash_inflow_total, cash_outflow_total, cash_balance_change,
       card_consume_total, recharge_card_consume, gift_card_consume,
       recharge_cash, first_recharge_cash, renewal_cash,
       order_count,
       created_at, updated_at
  FROM dws.dws_finance_area_daily
 WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;

COMMIT;

回滚前置条件

  • 确认无正在运行的 ETL loader 写入 member_order_count
  • 确认 apps/backend/app/ai/prompts/app2a_finance_area_prompt.pygit revert(否则查询缺列会报错)
  • 确认 FDW 映射同步回滚(若 FDW foreign table 显式列出 member_order_count需重建 foreign table

数据回填回滚

  • 新列默认 0 · 无需数据回填即可回滚
  • 若已执行过 scripts/ops/backfill_area_member_order.py(未来脚本),回滚后历史数据会丢失 member_order_count 值 · 但 DWD 原始订单数据在 dwd.dwd_orders 保留 · 可从 DWD 重新回填

4 · 验证 SQL

验证 1 · 列存在性

SELECT column_name,
       data_type,
       column_default,
       is_nullable
  FROM information_schema.columns
 WHERE table_schema = 'dws'
   AND table_name = 'dws_finance_area_daily'
   AND column_name = 'member_order_count';

-- 期望1 行 · integer · 0 · NO

验证 2 · 双 schema RLS 视图暴露新列

SELECT table_schema, table_name, column_name
  FROM information_schema.columns
 WHERE table_name = 'v_dws_finance_area_daily'
   AND column_name = 'member_order_count'
 ORDER BY table_schema;

-- 期望2 行 · (app, v_dws_finance_area_daily, member_order_count) + (dws, v_dws_finance_area_daily, member_order_count)

验证 3 · 全店 = 区域和ETL loader 改造完成后执行)

-- 设置测试门店 ID
SET app.current_site_id = 2790685415443269;

-- 比较最近 7 天 全店会员订单数 vs 区域会员订单数之和
SELECT s.site_id,
       s.stat_date,
       s.member_order_count                    AS full_store_member_orders,
       COALESCE(SUM(a.member_order_count), 0)  AS sum_area_member_orders,
       s.member_order_count - COALESCE(SUM(a.member_order_count), 0) AS diff
  FROM app.v_dws_finance_daily_summary s
  LEFT JOIN app.v_dws_finance_area_daily a
    ON s.site_id = a.site_id
   AND s.stat_date = a.stat_date
 WHERE s.stat_date >= CURRENT_DATE - INTERVAL '7 days'
 GROUP BY s.site_id, s.stat_date, s.member_order_count
HAVING s.member_order_count <> COALESCE(SUM(a.member_order_count), 0);

-- 期望0 行(全店会员订单数 = 所有区域之和)
-- 注:若 ETL loader 尚未改造,此 SQL 返回若干行属正常降级状态(区域侧全为 0

验证 4 · RLS 过滤正常工作

-- 未设置 app.current_site_id 时应返回空
RESET app.current_site_id;
SELECT COUNT(*) FROM app.v_dws_finance_area_daily;
-- 期望0 行 或 报错 "current_setting not found"

-- 设置后应返回该门店数据
SET app.current_site_id = 2790685415443269;
SELECT site_id, COUNT(*) FROM app.v_dws_finance_area_daily GROUP BY site_id;
-- 期望1 行(仅 site_id = 2790685415443269

5 · 关联变更

关联项 状态 说明
ETL loader 改造 待实施 · Phase B2 apps/etl/connectors/feiqiu/loaders/dws_finance_area_daily*.pyis_member_order 聚合
apps/backend/app/ai/prompts/app2a_finance_area_prompt.py 待实施 · Phase C 消费新列计算区域级会员占比
FDW 跨库映射刷新 待确认 zqyy_app 端通过 FDW 访问 etl_feiqiu.app.v_dws_finance_area_daily,需确认 foreign table 是否需重建
DWS 快照 DDL 同步 待执行 本迁移合并后运行 PYTHONUTF8=1 python tools/db/gen_consolidated_ddl.py 刷新 docs/database/ddl/ 快照

6 · 变更记录

日期 操作 执行人
2026-04-23 迁移脚本产出 Claude + Neo
待定 测试库执行 + 验证 SQL 通过 Neo
待定 生产库执行 Neo