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>
This commit is contained in:
@@ -0,0 +1,180 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- Migration: 20260423__app2a_add_member_order_count.sql
|
||||||
|
-- Purpose: 为 app2a 区域财务洞察增加 dws 层区域级会员订单数列
|
||||||
|
-- Related: docs/ai/app2_finance_multi_app_design.md v2 · Phase B
|
||||||
|
-- Author: Claude + Neo
|
||||||
|
-- =============================================================
|
||||||
|
--
|
||||||
|
-- 改动说明:
|
||||||
|
-- 1. dws.dws_finance_area_daily 增加 member_order_count 列(默认 0,允许新历史数据为 0)
|
||||||
|
-- 2. 重建 RLS 视图 app.v_dws_finance_area_daily,暴露新列
|
||||||
|
-- 3. 若存在 dws.v_dws_finance_area_daily 同名镜像视图,同步重建
|
||||||
|
--
|
||||||
|
-- 后置依赖:
|
||||||
|
-- - ETL loader apps/etl/connectors/feiqiu/loaders/dws_finance_area_daily.py
|
||||||
|
-- 需要同步改造:按 (area_code, stat_date) 聚合 DWD 订单时,
|
||||||
|
-- 将 is_member_order = true 的订单数合入 member_order_count
|
||||||
|
-- - 改造前历史数据 member_order_count = 0;改造后新增数据生效
|
||||||
|
-- - 如需历史回填:scripts/ops/backfill_area_member_order.py(本迁移不含)
|
||||||
|
--
|
||||||
|
-- 回滚策略:
|
||||||
|
-- 见文件末 ROLLBACK 段
|
||||||
|
--
|
||||||
|
-- 验证 SQL:
|
||||||
|
-- 见文件末 VERIFICATION 段
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Step 1: dws 层表加列
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
ALTER TABLE dws.dws_finance_area_daily
|
||||||
|
ADD COLUMN IF NOT EXISTS member_order_count integer NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN dws.dws_finance_area_daily.member_order_count IS
|
||||||
|
'会员订单数 (区域粒度, 从 DWD 聚合 is_member_order=true 的订单计数, 默认 0)';
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Step 2: 重建 app schema 的 RLS 视图
|
||||||
|
-- - 保留原 RLS 过滤条件 (current_setting('app.current_site_id'))
|
||||||
|
-- - PostgreSQL 限制:CREATE OR REPLACE VIEW 仅允许末尾加列,不能中间插入
|
||||||
|
-- - 故 member_order_count 加到末尾(逻辑归属仍为单位经济,顺序不影响语义)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
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,
|
||||||
|
member_order_count -- 新增列(PostgreSQL CREATE OR REPLACE VIEW 限制:只能末尾加)
|
||||||
|
FROM dws.dws_finance_area_daily
|
||||||
|
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||||
|
|
||||||
|
COMMENT ON VIEW app.v_dws_finance_area_daily IS
|
||||||
|
'区域级财务日粒度 RLS 视图 | app2a 区域财务洞察数据源 | 2026-04-23 新增 member_order_count';
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Step 3: 重建 dws schema 镜像 RLS 视图
|
||||||
|
-- (按 CLAUDE.md "RLS 视图双 Schema 规则" 要求,dws 和 app 两个 schema 的 RLS 视图必须同步)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
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,
|
||||||
|
member_order_count -- 新增列(PostgreSQL CREATE OR REPLACE VIEW 限制:只能末尾加)
|
||||||
|
FROM dws.dws_finance_area_daily
|
||||||
|
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||||
|
|
||||||
|
COMMENT ON VIEW dws.v_dws_finance_area_daily IS
|
||||||
|
'区域级财务日粒度 RLS 视图 (dws schema 镜像) | 与 app.v_dws_finance_area_daily 双 schema 规则要求下同步';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- =============================================================
|
||||||
|
-- VERIFICATION (事后手工执行)
|
||||||
|
-- =============================================================
|
||||||
|
-- 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. 视图暴露新列
|
||||||
|
-- SELECT column_name
|
||||||
|
-- FROM information_schema.columns
|
||||||
|
-- WHERE table_schema = 'app'
|
||||||
|
-- AND table_name = 'v_dws_finance_area_daily'
|
||||||
|
-- AND column_name = 'member_order_count';
|
||||||
|
-- -- 期望: 1 行
|
||||||
|
--
|
||||||
|
-- 3. 数据校验 (ETL loader 改造并跑一次后执行)
|
||||||
|
-- SET app.current_site_id = <测试门店 ID>;
|
||||||
|
-- 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 行 (全店会员订单数 = 区域之和)
|
||||||
|
|
||||||
|
-- =============================================================
|
||||||
|
-- ROLLBACK
|
||||||
|
-- =============================================================
|
||||||
|
-- BEGIN;
|
||||||
|
-- DROP VIEW IF EXISTS app.v_dws_finance_area_daily CASCADE;
|
||||||
|
-- DROP VIEW IF EXISTS dws.v_dws_finance_area_daily CASCADE;
|
||||||
|
-- ALTER TABLE dws.dws_finance_area_daily DROP COLUMN IF EXISTS member_order_count;
|
||||||
|
-- -- 重新 CREATE VIEW app.v_dws_finance_area_daily (不含新列) - 从 db/etl_feiqiu/schemas/app.sql:714 复制
|
||||||
|
-- COMMIT;
|
||||||
70
db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql
Normal file
70
db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- Migration: 20260423__ai_cache_allow_app2a.sql
|
||||||
|
-- Purpose: 放开 biz.ai_cache.chk_ai_cache_type CHECK 约束,
|
||||||
|
-- 允许新 cache_type 'app2a_finance_area'(app2a 区域财务洞察)
|
||||||
|
-- Related: docs/ai/app2_finance_multi_app_design.md v2 · Phase F
|
||||||
|
-- Author: Claude + Neo
|
||||||
|
-- =============================================================
|
||||||
|
--
|
||||||
|
-- 改动说明:
|
||||||
|
-- 1. DROP 旧 CHECK 约束 chk_ai_cache_type
|
||||||
|
-- 2. CREATE 新 CHECK 约束,加入 'app2a_finance_area' 值
|
||||||
|
--
|
||||||
|
-- 兼容性:
|
||||||
|
-- - 旧 cache_type 值全部保留,仅新增一个允许值
|
||||||
|
-- - 无需数据迁移
|
||||||
|
--
|
||||||
|
-- 回滚策略:
|
||||||
|
-- 见文件末 ROLLBACK 段
|
||||||
|
--
|
||||||
|
-- 验证 SQL:
|
||||||
|
-- 见文件末 VERIFICATION 段
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 1. DROP 旧约束
|
||||||
|
ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
|
||||||
|
|
||||||
|
-- 2. CREATE 新约束(加入 app2a_finance_area)
|
||||||
|
ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type
|
||||||
|
CHECK (cache_type IN (
|
||||||
|
'app2_finance',
|
||||||
|
'app2a_finance_area', -- 2026-04-23 新增(区域财务洞察 · 64 组合)
|
||||||
|
'app3_clue',
|
||||||
|
'app4_analysis',
|
||||||
|
'app5_tactics',
|
||||||
|
'app6_note_analysis',
|
||||||
|
'app7_customer_analysis',
|
||||||
|
'app8_clue_consolidated'
|
||||||
|
));
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- =============================================================
|
||||||
|
-- VERIFICATION(事后手工执行)
|
||||||
|
-- =============================================================
|
||||||
|
-- 1. 约束允许值检查
|
||||||
|
-- SELECT pg_get_constraintdef(oid)
|
||||||
|
-- FROM pg_constraint
|
||||||
|
-- WHERE conname = 'chk_ai_cache_type';
|
||||||
|
-- -- 期望:返回的 CHECK 里包含 'app2a_finance_area'
|
||||||
|
--
|
||||||
|
-- 2. 插入新 cache_type 应成功
|
||||||
|
-- INSERT INTO biz.ai_cache (cache_type, site_id, target_id, result_json, status, expires_at)
|
||||||
|
-- VALUES ('app2a_finance_area', 1, 'test__all', '{}', 'valid', NOW() + INTERVAL '1 day');
|
||||||
|
-- -- 期望:成功插入
|
||||||
|
-- DELETE FROM biz.ai_cache WHERE cache_type = 'app2a_finance_area' AND site_id = 1 AND target_id = 'test__all';
|
||||||
|
|
||||||
|
-- =============================================================
|
||||||
|
-- ROLLBACK
|
||||||
|
-- =============================================================
|
||||||
|
-- BEGIN;
|
||||||
|
-- ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
|
||||||
|
-- ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type
|
||||||
|
-- CHECK (cache_type IN (
|
||||||
|
-- 'app2_finance', 'app3_clue', 'app4_analysis', 'app5_tactics',
|
||||||
|
-- 'app6_note_analysis', 'app7_customer_analysis', 'app8_clue_consolidated'
|
||||||
|
-- ));
|
||||||
|
-- COMMIT;
|
||||||
|
-- 注:回滚前必须先 DELETE 所有 cache_type = 'app2a_finance_area' 的记录,否则 CHECK 会失败
|
||||||
120
docs/database/changes/2026-04-23__ai_cache_allow_app2a.md
Normal file
120
docs/database/changes/2026-04-23__ai_cache_allow_app2a.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# 数据库变更:ai_cache.chk_ai_cache_type 约束放开 app2a_finance_area
|
||||||
|
|
||||||
|
> 日期:2026-04-23
|
||||||
|
> 迁移脚本:[db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql](../../db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql)
|
||||||
|
> 相关设计:[docs/ai/app2_finance_multi_app_design.md](../ai/app2_finance_multi_app_design.md) · Phase F
|
||||||
|
> 涉及库:`zqyy_app`(biz schema)
|
||||||
|
> 风险等级:**低**(仅放开约束,不影响已有数据)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1 · 变更说明
|
||||||
|
|
||||||
|
### 约束变更
|
||||||
|
|
||||||
|
| Schema.Table | 约束名 | 变更类型 | 允许值 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `biz.ai_cache` | `chk_ai_cache_type` | **替换**(DROP + CREATE) | 旧 7 项 + 新增 `app2a_finance_area` |
|
||||||
|
|
||||||
|
**新约束完整定义**:
|
||||||
|
```sql
|
||||||
|
CHECK (cache_type IN (
|
||||||
|
'app2_finance',
|
||||||
|
'app2a_finance_area', -- 新增
|
||||||
|
'app3_clue',
|
||||||
|
'app4_analysis',
|
||||||
|
'app5_tactics',
|
||||||
|
'app6_note_analysis',
|
||||||
|
'app7_customer_analysis',
|
||||||
|
'app8_clue_consolidated'
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他检查
|
||||||
|
|
||||||
|
- `ai_run_logs.app_type` · **无 CHECK 约束**(VARCHAR(30)),无需改动
|
||||||
|
- `ai_trigger_jobs.*` · **无相关 CHECK 约束**,无需改动
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2 · 兼容性影响
|
||||||
|
|
||||||
|
### 对已有数据
|
||||||
|
|
||||||
|
- 旧 7 项 cache_type 值全部保留,现有数据不动
|
||||||
|
- 无需数据迁移
|
||||||
|
|
||||||
|
### 对后端代码
|
||||||
|
|
||||||
|
- `CacheTypeEnum.APP2A_FINANCE_AREA` 枚举已在 `apps/backend/app/ai/schemas.py` 添加
|
||||||
|
- `AICacheService.write_cache(cache_type='app2a_finance_area', ...)` 本约束放开后即可成功写入
|
||||||
|
- `_SUPPORTED_APP_TYPES` / `dispatcher.run_single_app` / `build_app2a_area_prompt` 链路在 Phase C 已完成
|
||||||
|
|
||||||
|
### 对小程序
|
||||||
|
|
||||||
|
- [board-finance.ts](../../apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts) `_loadAIInsights` 已按 area 动态选 `cache_type`
|
||||||
|
- 后端 xcx API `/api/ai/cache/{cache_type}` 白名单自动涵盖(基于 `CacheTypeEnum`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3 · 回滚策略
|
||||||
|
|
||||||
|
### 回滚前置条件
|
||||||
|
|
||||||
|
**必须先删除所有 `cache_type = 'app2a_finance_area'` 的记录**,否则 CHECK 会拒绝。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 回滚预备
|
||||||
|
DELETE FROM biz.ai_cache WHERE cache_type = 'app2a_finance_area';
|
||||||
|
|
||||||
|
-- 回滚 DDL
|
||||||
|
BEGIN;
|
||||||
|
ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
|
||||||
|
ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type
|
||||||
|
CHECK (cache_type IN (
|
||||||
|
'app2_finance', 'app3_clue', 'app4_analysis', 'app5_tactics',
|
||||||
|
'app6_note_analysis', 'app7_customer_analysis', 'app8_clue_consolidated'
|
||||||
|
));
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4 · 验证 SQL
|
||||||
|
|
||||||
|
### 验证 1 · 约束允许值包含 app2a_finance_area
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT pg_get_constraintdef(oid)
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'chk_ai_cache_type';
|
||||||
|
-- 期望:返回的 CHECK 中含 'app2a_finance_area'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证 2 · 插入新 cache_type 应成功
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO biz.ai_cache (cache_type, site_id, target_id, result_json, status, expires_at)
|
||||||
|
VALUES ('app2a_finance_area', 1, 'test__all', '{}'::jsonb, 'valid', NOW() + INTERVAL '1 day');
|
||||||
|
-- 期望:成功插入
|
||||||
|
-- 清理:
|
||||||
|
DELETE FROM biz.ai_cache WHERE cache_type = 'app2a_finance_area' AND site_id = 1 AND target_id = 'test__all';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证 3 · 旧 cache_type 插入仍正常
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO biz.ai_cache (cache_type, site_id, target_id, result_json, status, expires_at)
|
||||||
|
VALUES ('app2_finance', 1, 'test__backward_compat', '{}'::jsonb, 'valid', NOW() + INTERVAL '1 day');
|
||||||
|
-- 期望:成功插入
|
||||||
|
DELETE FROM biz.ai_cache WHERE cache_type = 'app2_finance' AND site_id = 1 AND target_id = 'test__backward_compat';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5 · 变更记录
|
||||||
|
|
||||||
|
| 日期 | 操作 | 执行人 |
|
||||||
|
|---|---|---|
|
||||||
|
| 2026-04-23 | 迁移脚本产出 + 测试库执行通过 | Claude + Neo |
|
||||||
|
| 待定 | 生产库执行 | Neo(上线 checklist) |
|
||||||
214
docs/database/changes/2026-04-23__app2a_member_order_count.md
Normal file
214
docs/database/changes/2026-04-23__app2a_member_order_count.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# 数据库变更:dws_finance_area_daily 增加 member_order_count 列
|
||||||
|
|
||||||
|
> 日期:2026-04-23
|
||||||
|
> 迁移脚本:[db/etl_feiqiu/migrations/20260423__app2a_add_member_order_count.sql](../../db/etl_feiqiu/migrations/20260423__app2a_add_member_order_count.sql)
|
||||||
|
> 相关设计文档:[docs/ai/app2_finance_multi_app_design.md](../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 · 回滚策略
|
||||||
|
|
||||||
|
### 回滚步骤(按顺序执行)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
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.py` 已 `git 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 · 列存在性
|
||||||
|
|
||||||
|
```sql
|
||||||
|
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 视图暴露新列
|
||||||
|
|
||||||
|
```sql
|
||||||
|
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 改造完成后执行)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 设置测试门店 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 过滤正常工作
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 未设置 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*.py` 按 `is_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 |
|
||||||
Reference in New Issue
Block a user