微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
203
docs/database/BD_Manual_20260301_cleanup_and_fixes.md
Normal file
203
docs/database/BD_Manual_20260301_cleanup_and_fixes.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# BD 手册:assistant_trash_event 清理 + ODS_STORE_GOODS_SALES 修复 + dim_staff_ex 修复 + DWS 精度扩展 + ODS 库存 siteid 注入
|
||||
|
||||
> 日期:2026-03-01
|
||||
> Prompt 摘要:清理 assistant_trash_event 残留代码/DDL 文档;修复 ODS_STORE_GOODS_SALES 窗口配置以恢复商品销售数据拉取;修复 dim_staff_ex FACT_MAPPINGS 列名映射错误;DWS 层 7 个 ratio/margin 字段 numeric 精度扩展(P1);ODS goods_stock_summary 加 siteid 列 + DWD 映射补全(P2)
|
||||
> 直接原因:联调发现的 P1/P2 问题 + 三个独立问题的批量修复
|
||||
|
||||
---
|
||||
|
||||
## 一、变更说明
|
||||
|
||||
### 1.1 assistant_trash_event 残留清理
|
||||
|
||||
表 `dwd.dwd_assistant_trash_event` 和 `dwd.dwd_assistant_trash_event_ex` 已于 2026-02-22 DROP,
|
||||
上游 ODS 表 `ods.assistant_cancellation_records` 同步 DROP。本次清理残留的代码引用和 DDL 文档。
|
||||
|
||||
**DDL 文档删除项:**
|
||||
|
||||
| 文件 | 删除内容 |
|
||||
|------|---------|
|
||||
| `etl_feiqiu__ods.sql` | PK 约束 `assistant_cancellation_records_pkey`,索引 `idx_assistant_cancellation_records_fetched_at_*`(2 条) |
|
||||
| `etl_feiqiu__dwd.sql` | PK 约束 `dwd_assistant_trash_event_pkey`、`dwd_assistant_trash_event_ex_pkey`,索引 `idx_dwd_assistant_trash_event_*`(2 条) |
|
||||
| `dwd-amount-duration-calibration.md` | 章节 2.11(助教废除事件主表)、存疑字段 #15、数据新鲜度行 |
|
||||
|
||||
**代码删除项(前序已完成):**
|
||||
- `utils/json_store.py` — API 路径映射
|
||||
- `tasks/utility/manual_ingest_task.py` — FILE_MAPPING / TABLE_SPECS
|
||||
- `quality/consistency_checker.py` — ODS_TABLE_TO_JSON_FILE / ODS_TABLE_TO_TASK_CODE
|
||||
- `scripts/refresh_json_and_audit.py` — ACTUAL_LIST_KEY
|
||||
- `scripts/run_compare_v3.py` / `run_compare_v3_fixed.py` — TABLES 列表
|
||||
|
||||
### 1.2 ODS_STORE_GOODS_SALES 窗口配置修复
|
||||
|
||||
**问题:** `ods_tasks.py` 中 `ODS_STORE_GOODS_SALES` 的 `requires_window=False`,导致 API `/TenantGoods/GetGoodsSalesList` 不传 `startTime/endTime` 参数,始终返回 0 条记录。
|
||||
|
||||
**修复:**
|
||||
- `requires_window` → `True`
|
||||
- 新增 `time_fields=("startTime", "endTime")`
|
||||
|
||||
**数据恢复:** 以 30 天窗口切分,回填 2025-07-07 ~ 2026-03-01,ODS 新增 26,759 条,DWD 层 `dwd_store_goods_sale` 从 17,563 → 26,759 条,时间范围延伸至 2026-02-25。
|
||||
|
||||
### 1.3 dim_staff_ex FACT_MAPPINGS 列名修复
|
||||
|
||||
**问题:** `dwd_load_task.py` 中 `dim_staff_ex` 的 FACT_MAPPINGS 使用驼峰列名(`cashierpointid`、`groupid` 等),但 ODS 表 `staff_info_master` 实际列名为下划线风格(`cashier_point_id`、`group_id` 等),导致 SCD2 合并 SQL 执行报错,整表被跳过。
|
||||
|
||||
**修复映射:**
|
||||
|
||||
| DWD 列 | 修复前(错误) | 修复后(正确) |
|
||||
|--------|---------------|---------------|
|
||||
| `cashier_point_id` | `cashierpointid` | `cashier_point_id` |
|
||||
| `cashier_point_name` | `cashierpointname` | `cashier_point_name` |
|
||||
| `group_id` | `groupid` | `group_id` |
|
||||
| `group_name` | `groupname` | `group_name` |
|
||||
| `system_user_id` | `systemuserid` | `system_user_id` |
|
||||
| `tenant_org_id` | `tenantorgid` | `tenant_org_id` |
|
||||
| `user_roles` | `userroles` | `user_roles` |
|
||||
|
||||
**数据恢复:** DWD 装载后 `dim_staff_ex` 从 0 行 → 15 行。
|
||||
|
||||
### 1.4 [P1] DWS 层 numeric 精度扩展(举一反三)
|
||||
|
||||
**问题:** `dws.dws_assistant_finance_analysis.gross_margin` 定义为 `numeric(5,4)`,只能存 ±0.9999。当 `cost_daily > revenue_total`(亏损场景),`gross_margin = gross_profit / revenue_total` 可能 < -1,导致 INSERT 溢出报错。举一反三排查发现 7 个同类风险字段。
|
||||
|
||||
**修复:**
|
||||
|
||||
| 表 | 字段 | 修复前 | 修复后 |
|
||||
|----|------|--------|--------|
|
||||
| `dws.cfg_performance_tier` | `bonus_deduction_ratio` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_assistant_finance_analysis` | `gross_margin` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_assistant_recharge_commission` | `commission_ratio` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_assistant_salary_calc` | `bonus_deduction_ratio` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_finance_discount_detail` | `discount_ratio` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_finance_income_structure` | `income_ratio` | `numeric(5,4)` | `numeric(7,4)` |
|
||||
| `dws.dws_member_assistant_intimacy` | `burst_multiplier` | `numeric(6,4)` | `numeric(7,4)` |
|
||||
|
||||
**依赖视图处理:** 7 个 `app.v_dws_*` RLS 视图先 DROP 再重建。
|
||||
|
||||
**Python 防御:** `assistant_finance_task.py` 中 `gross_margin` 计算加 clamp 到 ±999.9999。
|
||||
|
||||
**迁移脚本:** `db/etl_feiqiu/migrations/20260301_dws_numeric_precision_fix.sql`
|
||||
|
||||
### 1.5 [P2] ODS goods_stock_summary 加 siteid + DWD 映射补全
|
||||
|
||||
**问题:** `dwd.dwd_goods_stock_summary` DDL 定义了 `site_id bigint` 和 `tenant_id bigint`,但 FACT_MAPPINGS 缺少映射,导致 DWD 层 site_id 始终为 NULL。
|
||||
|
||||
**根因分析:**
|
||||
- API `GetGoodsStockReport` 返回的记录不含 `siteId`/`tenantId`(已从 JSON 缓存确认)
|
||||
- ODS 表 `ods.goods_stock_summary` 也没有 `siteid` 列
|
||||
- 请求参数中有 `siteId`,但 ODS 入库时未注入到记录中
|
||||
|
||||
**修复(三层联动):**
|
||||
1. ODS 表加列:`ALTER TABLE ods.goods_stock_summary ADD COLUMN siteid bigint`
|
||||
2. ODS 入库通用注入:`_insert_records_schema_aware` 中,当 ODS 表有 `siteid` 列但记录不含时,从 `app.store_id` 配置注入
|
||||
3. DWD FACT_MAPPINGS 补映射:`("site_id", '"siteid"', "bigint")`
|
||||
4. 已有数据回填:从 `ods.goods_stock_movements` 推断 siteid 回填 3216 条
|
||||
|
||||
**迁移脚本:** `db/etl_feiqiu/migrations/20260301_ods_goods_stock_summary_add_siteid.sql`
|
||||
|
||||
---
|
||||
|
||||
## 二、兼容性影响
|
||||
|
||||
| 子系统 | 影响 |
|
||||
|--------|------|
|
||||
| ETL | `assistant_trash_event` 相关任务已无代码引用,无影响;`ODS_STORE_GOODS_SALES` 恢复正常窗口拉取;`dim_staff_ex` 恢复正常 SCD2 装载;DWS ratio 字段精度扩展后不再溢出;ODS 库存汇总入库时自动注入 siteid |
|
||||
| 后端 API | 7 个 `app.v_dws_*` RLS 视图已重建,字段类型从 numeric(5,4) 变为 numeric(7,4),API 返回值精度不变(仍为 4 位小数),无破坏性影响 |
|
||||
| 小程序 | 无影响 |
|
||||
| 管理后台 | 商品销售相关报表数据将恢复完整;库存汇总将携带 site_id |
|
||||
|
||||
---
|
||||
|
||||
## 三、回滚策略
|
||||
|
||||
### 3.1 assistant_trash_event
|
||||
纯文档清理,无需回滚。如需恢复,从 git 历史还原对应行即可。
|
||||
|
||||
### 3.2 ODS_STORE_GOODS_SALES
|
||||
```python
|
||||
# 回滚 ods_tasks.py:
|
||||
requires_window=True → requires_window=False
|
||||
# 删除 time_fields=("startTime", "endTime") 行
|
||||
```
|
||||
|
||||
### 3.3 dim_staff_ex
|
||||
```python
|
||||
# 回滚 dwd_load_task.py FACT_MAPPINGS:
|
||||
("cashier_point_id", "cashier_point_id", "bigint") → ("cashier_point_id", "cashierpointid", "bigint")
|
||||
# 其余 6 个字段同理恢复驼峰写法
|
||||
```
|
||||
如需清空已装载数据:`TRUNCATE dwd.dim_staff_ex;`
|
||||
|
||||
### 3.4 [P1] DWS numeric 精度
|
||||
```sql
|
||||
-- 回滚脚本:db/etl_feiqiu/migrations/20260301_dws_numeric_precision_fix_rollback.sql
|
||||
-- 注意:如果已有数据超出原精度范围(如 gross_margin > 0.9999),回滚会失败
|
||||
-- 需先清理超范围数据:
|
||||
UPDATE dws.dws_assistant_finance_analysis SET gross_margin = LEAST(0.9999, GREATEST(-0.9999, gross_margin));
|
||||
-- 然后执行回滚(同样需要先 DROP 再重建视图)
|
||||
```
|
||||
Python 回滚:删除 `assistant_finance_task.py` 中的 clamp 行。
|
||||
|
||||
### 3.5 [P2] ODS siteid 列
|
||||
```sql
|
||||
-- ODS 列不可逆删除(已有数据依赖),但可置 NULL:
|
||||
UPDATE ods.goods_stock_summary SET siteid = NULL;
|
||||
-- DWD FACT_MAPPINGS 回滚:删除 ("site_id", '"siteid"', "bigint") 行
|
||||
-- ODS 入库注入回滚:删除 ods_tasks.py 中 "通用 siteid 注入" 代码块
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 assistant_trash_event 表已不存在
|
||||
SELECT count(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'dwd' AND table_name LIKE '%assistant_trash_event%';
|
||||
-- 预期:0
|
||||
|
||||
-- 2. 确认 ODS 商品销售数据已回填
|
||||
SELECT count(*) as cnt, min(create_time) as min_time, max(create_time) as max_time
|
||||
FROM ods.store_goods_sales_records WHERE fetched_at IS NOT NULL;
|
||||
-- 预期:cnt > 40000, max_time >= 2026-02-25
|
||||
|
||||
-- 3. 确认 DWD 商品销售数据已更新
|
||||
SELECT count(*) as cnt, max(create_time) as max_time FROM dwd.dwd_store_goods_sale;
|
||||
-- 预期:cnt > 25000, max_time >= 2026-02-25
|
||||
|
||||
-- 4. 确认 dim_staff_ex 已有数据
|
||||
SELECT count(*) as cnt FROM dwd.dim_staff_ex WHERE scd2_is_current = 1;
|
||||
-- 预期:cnt = 15(与 dim_staff 一致)
|
||||
|
||||
-- 5. 确认 dim_staff_ex 关键字段非全 NULL
|
||||
SELECT count(*) as has_system_user FROM dwd.dim_staff_ex
|
||||
WHERE scd2_is_current = 1 AND system_user_id IS NOT NULL;
|
||||
-- 预期:> 0
|
||||
|
||||
-- 6. [P1] 确认 DWS ratio 字段精度已扩展
|
||||
SELECT table_name, column_name, numeric_precision, numeric_scale
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dws'
|
||||
AND column_name IN ('gross_margin', 'bonus_deduction_ratio', 'commission_ratio',
|
||||
'discount_ratio', 'income_ratio', 'burst_multiplier')
|
||||
ORDER BY table_name;
|
||||
-- 预期:所有行 numeric_precision=7, numeric_scale=4
|
||||
|
||||
-- 7. [P1] 确认 RLS 视图已重建
|
||||
SELECT table_schema || '.' || table_name FROM information_schema.views
|
||||
WHERE table_schema = 'app'
|
||||
AND table_name IN ('v_cfg_performance_tier', 'v_dws_assistant_finance_analysis',
|
||||
'v_dws_assistant_recharge_commission', 'v_dws_assistant_salary_calc',
|
||||
'v_dws_finance_discount_detail', 'v_dws_finance_income_structure',
|
||||
'v_dws_member_assistant_intimacy');
|
||||
-- 预期:7 行
|
||||
|
||||
-- 8. [P2] 确认 ODS goods_stock_summary 有 siteid 列且已回填
|
||||
SELECT count(*) as total, count(siteid) as filled, count(DISTINCT siteid) as distinct_sites
|
||||
FROM ods.goods_stock_summary;
|
||||
-- 预期:filled = total, distinct_sites = 1
|
||||
|
||||
-- 9. [P2] 确认 DWD FACT_MAPPINGS 生效(下次 DWD_LOAD 后验证)
|
||||
SELECT count(*) as has_site_id FROM dwd.dwd_goods_stock_summary WHERE site_id IS NOT NULL;
|
||||
-- 预期:下次 ETL 运行后 > 0(当前可能仍为 0,需重跑 DWD_LOAD)
|
||||
```
|
||||
166
docs/database/BD_Manual_ai_tables.md
Normal file
166
docs/database/BD_Manual_ai_tables.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# BD_Manual:biz Schema AI 表(对话记录 + 消息 + 缓存)
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:`db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql`
|
||||
> 关联 SPEC:`05-miniapp-ai-integration`(P5 AI 集成层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(3 张)
|
||||
|
||||
| # | 表名 | 用途 | 字段数 |
|
||||
|---|------|------|--------|
|
||||
| 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 |
|
||||
| 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 |
|
||||
| 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 |
|
||||
|
||||
### 表字段明细
|
||||
|
||||
#### biz.ai_conversations(8 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `user_id` | VARCHAR(50) | NOT NULL | 用户 ID,系统自动调用时为 `system` |
|
||||
| `nickname` | VARCHAR(100) | NOT NULL DEFAULT '' | 用户昵称 |
|
||||
| `app_id` | VARCHAR(30) | NOT NULL | 应用标识:app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) |
|
||||
| `source_page` | VARCHAR(100) | 可空 | 来源页面标识 |
|
||||
| `source_context` | JSONB | 可空 | 页面上下文 JSON |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
|
||||
#### biz.ai_messages(6 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `conversation_id` | BIGINT | NOT NULL, FK → `biz.ai_conversations(id)` ON DELETE CASCADE | 关联对话 |
|
||||
| `role` | VARCHAR(10) | NOT NULL, CHECK IN ('user', 'assistant', 'system') | 消息角色 |
|
||||
| `content` | TEXT | NOT NULL | 消息内容 |
|
||||
| `tokens_used` | INTEGER | 可空 | 本条消息消耗的 token 数 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
|
||||
#### biz.ai_cache(9 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `cache_type` | VARCHAR(30) | NOT NULL, CHECK 7 个枚举值 | 缓存类型(见下方枚举) |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) |
|
||||
| `target_id` | VARCHAR(100) | NOT NULL | 目标 ID,含义因 cache_type 而异 |
|
||||
| `result_json` | JSONB | NOT NULL | 结构化输出结果 |
|
||||
| `score` | INTEGER | 可空 | 评分:仅应用 6 使用(1-10 分) |
|
||||
| `triggered_by` | VARCHAR(100) | 可空 | 触发来源标识 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| `expires_at` | TIMESTAMPTZ | 可空 | 可选过期时间 |
|
||||
|
||||
### cache_type 枚举值与 target_id 约定
|
||||
|
||||
| cache_type | target_id 格式 | 示例 |
|
||||
|---|---|---|
|
||||
| `app2_finance` | 时间维度编码 | `this_month`, `last_week` |
|
||||
| `app3_clue` | member_id | `12345` |
|
||||
| `app4_analysis` | `{assistant_id}_{member_id}` | `100_12345` |
|
||||
| `app5_tactics` | `{assistant_id}_{member_id}` | `100_12345` |
|
||||
| `app6_note_analysis` | member_id | `12345` |
|
||||
| `app7_customer_analysis` | member_id | `12345` |
|
||||
| `app8_clue_consolidated` | member_id | `12345` |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
|----|-----------|------|------|
|
||||
| `ai_conversations` | `idx_ai_conv_user_site` | INDEX | `(user_id, site_id, created_at DESC)` — 用户历史对话列表查询 |
|
||||
| `ai_conversations` | `idx_ai_conv_app_site` | INDEX | `(app_id, site_id, created_at DESC)` — 按应用查询对话 |
|
||||
| `ai_messages` | FK `conversation_id` | FK | → `biz.ai_conversations(id)` ON DELETE CASCADE |
|
||||
| `ai_messages` | `chk_ai_msg_role` | CHECK | `role IN ('user', 'assistant', 'system')` |
|
||||
| `ai_messages` | `idx_ai_msg_conv` | INDEX | `(conversation_id, created_at)` — 对话消息列表 |
|
||||
| `ai_cache` | `chk_ai_cache_type` | CHECK | 7 个枚举值 |
|
||||
| `ai_cache` | `idx_ai_cache_lookup` | INDEX | `(cache_type, site_id, target_id, created_at DESC)` — 查询最新缓存 |
|
||||
| `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录(ASC 排序便于删除最旧) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。AI 表属于 `biz` Schema,不参与 ETL 流程 |
|
||||
| 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`)将基于这三张表实现对话持久化、缓存读写、SSE 流式对话等功能 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 AI API 间接使用对话和缓存数据 |
|
||||
| 管理后台 | 暂无影响。后续可能增加 AI 调用统计和缓存管理界面 |
|
||||
| `member_retention_clue` | 间接关联。App8(维客线索整理)的结果同时写入 `ai_cache` 和 `member_retention_clue` 表 |
|
||||
| 现有 `biz` Schema | 兼容。仅新增 3 张表,不修改已有对象(coach_tasks、notes、trigger_jobs 等) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
按逆序 DROP(CASCADE 处理外键依赖):
|
||||
|
||||
```sql
|
||||
-- 删除索引
|
||||
DROP INDEX IF EXISTS biz.idx_ai_cache_cleanup;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_cache_lookup;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_msg_conv;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_conv_app_site;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_conv_user_site;
|
||||
|
||||
-- 删除表(ai_messages 依赖 ai_conversations,需先删或用 CASCADE)
|
||||
DROP TABLE IF EXISTS biz.ai_cache CASCADE;
|
||||
DROP TABLE IF EXISTS biz.ai_messages CASCADE;
|
||||
DROP TABLE IF EXISTS biz.ai_conversations CASCADE;
|
||||
```
|
||||
|
||||
注意:
|
||||
- `ai_messages` 通过 FK 依赖 `ai_conversations`,CASCADE 会级联删除消息
|
||||
- 如果表中已有数据,需先备份再执行回滚
|
||||
- 回滚不会删除 `biz` Schema 本身
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 3 张 AI 表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'biz'
|
||||
AND table_name IN ('ai_conversations', 'ai_messages', 'ai_cache')
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 3 行(ai_cache, ai_conversations, ai_messages)
|
||||
|
||||
-- 2. 验证 ai_conversations 字段数量和关键字段
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 8 行,包含 id/user_id/nickname/app_id/site_id/source_page/source_context/created_at
|
||||
|
||||
-- 3. 验证 ai_messages 的外键和 CHECK 约束
|
||||
SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.ai_messages'::regclass
|
||||
ORDER BY conname;
|
||||
-- 预期:包含 chk_ai_msg_role(CHECK role IN ...)和 ai_messages_conversation_id_fkey(FK → ai_conversations)
|
||||
|
||||
-- 4. 验证 ai_cache 的 CHECK 约束(7 个枚举值)
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c';
|
||||
-- 预期:返回 1 行 chk_ai_cache_type,包含 7 个枚举值
|
||||
|
||||
-- 5. 验证索引全部存在(5 个)
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz'
|
||||
AND indexname IN (
|
||||
'idx_ai_conv_user_site', 'idx_ai_conv_app_site',
|
||||
'idx_ai_msg_conv',
|
||||
'idx_ai_cache_lookup', 'idx_ai_cache_cleanup'
|
||||
)
|
||||
ORDER BY indexname;
|
||||
-- 预期:返回 5 行
|
||||
```
|
||||
@@ -94,6 +94,7 @@
|
||||
| 日期 | 字段 | 修正内容 |
|
||||
|------|------|---------|
|
||||
| 2026-02-20 | `site_assistant_id` | ODS 源从 `order_assistant_id`(订单级 ID)修正为 `site_assistant_id`(助教档案 ID) |
|
||||
| 2026-02-26 | (下游)`table_area_name` | DWS 任务 `_extract_service_records()` 原错误引用 `asl.table_area_name`(本表无此列),改为 JOIN `dwd.dim_table` 取 `site_table_area_name`。详见 `BD_Manual_fix_dws_assistant_daily_table_area.md` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
186
docs/database/BD_Manual_biz_date_function_and_mv_rebuild.md
Normal file
186
docs/database/BD_Manual_biz_date_function_and_mv_rebuild.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# BD_Manual:dws.biz_date() 函数与物化视图营业日重建
|
||||
|
||||
> 变更类型:新增函数 + 重建物化视图
|
||||
> Schema:`dws`
|
||||
> 迁移脚本 1:`db/etl_feiqiu/migrations/2026-02-27__add_biz_date_function.sql`
|
||||
> 迁移脚本 2:`db/etl_feiqiu/migrations/2026-02-27__rebuild_mv_with_biz_date.sql`
|
||||
> DDL 基线:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 关联需求:Requirements 9.1, 9.2, 9.3, 9.4
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 1.1 新增函数:`dws.biz_date(timestamptz, int)`
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 函数签名 | `dws.biz_date(ts timestamptz, cutoff_hour int DEFAULT 8) RETURNS date` |
|
||||
| 语言 | SQL |
|
||||
| 特性 | `IMMUTABLE PARALLEL SAFE` |
|
||||
| 逻辑 | `(ts - make_interval(hours => cutoff_hour))::date` |
|
||||
| 等价 Python | `neozqyy_shared.datetime_utils.business_date()` |
|
||||
|
||||
将时间戳减去 `cutoff_hour` 小时后取日期,实现营业日归属。默认 `cutoff_hour=8`,即 08:00 前的时间戳归属前一天。
|
||||
|
||||
### 1.2 重建物化视图(8 个)
|
||||
|
||||
将 `CURRENT_DATE` 替换为 `dws.biz_date(NOW())`,使物化视图的数据范围与 DWS 任务的营业日口径一致。
|
||||
|
||||
| 物化视图 | 原条件 | 新条件 |
|
||||
|---------|--------|--------|
|
||||
| `mv_dws_assistant_daily_detail_l1` | `stat_date >= (CURRENT_DATE - '1 day')` | `stat_date >= (dws.biz_date(NOW()) - '1 day')` |
|
||||
| `mv_dws_assistant_daily_detail_l2` | `stat_date >= (CURRENT_DATE - '30 days')` | `stat_date >= (dws.biz_date(NOW()) - '30 days')` |
|
||||
| `mv_dws_assistant_daily_detail_l3` | `stat_date >= (CURRENT_DATE - '90 days')` | `stat_date >= (dws.biz_date(NOW()) - '90 days')` |
|
||||
| `mv_dws_assistant_daily_detail_l4` | `date_trunc('month', CURRENT_DATE) ± 6 mons` | `date_trunc('month', dws.biz_date(NOW())) ± 6 mons` |
|
||||
| `mv_dws_finance_daily_summary_l1` | `stat_date >= (CURRENT_DATE - '1 day')` | `stat_date >= (dws.biz_date(NOW()) - '1 day')` |
|
||||
| `mv_dws_finance_daily_summary_l2` | `stat_date >= (CURRENT_DATE - '30 days')` | `stat_date >= (dws.biz_date(NOW()) - '30 days')` |
|
||||
| `mv_dws_finance_daily_summary_l3` | `stat_date >= (CURRENT_DATE - '90 days')` | `stat_date >= (dws.biz_date(NOW()) - '90 days')` |
|
||||
| `mv_dws_finance_daily_summary_l4` | `date_trunc('month', CURRENT_DATE) ± 6 mons` | `date_trunc('month', dws.biz_date(NOW())) ± 6 mons` |
|
||||
|
||||
索引在重建后重新创建,结构不变。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | `MvRefreshTask`(`DWS_MV_REFRESH_*`)执行 `REFRESH MATERIALIZED VIEW` 不受影响,视图定义变更对刷新逻辑透明 |
|
||||
| 后端 API | 无直接影响。后端通过 DWS 表查询,物化视图仅用于加速查询 |
|
||||
| 管理后台 | 无影响。前端不直接查询物化视图 |
|
||||
| 小程序 | 无影响 |
|
||||
| 字段映射 | 物化视图列结构不变(`SELECT *`),仅 WHERE 条件变更 |
|
||||
| `biz_date()` 函数 | 标记为 `IMMUTABLE`,可安全用于索引表达式和物化视图定义 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
### 3.1 回滚函数
|
||||
|
||||
```sql
|
||||
DROP FUNCTION IF EXISTS dws.biz_date(timestamptz, int);
|
||||
```
|
||||
|
||||
> 注意:需先回滚物化视图(3.2),否则依赖此函数的视图定义会阻止删除。
|
||||
|
||||
### 3.2 回滚物化视图(恢复自然日口径)
|
||||
|
||||
使用 `scripts/migrate/migrate_finalize.py` 中的原始定义重建:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
|
||||
|
||||
-- 用原始定义重建(CURRENT_DATE 版本)
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l1 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (CURRENT_DATE - '1 day'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l2 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (CURRENT_DATE - '30 days'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l3 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (CURRENT_DATE - '90 days'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l4 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (date_trunc('month', CURRENT_DATE::timestamptz) - '6 mons'::interval)
|
||||
AND stat_date < date_trunc('month', CURRENT_DATE::timestamptz) WITH DATA;
|
||||
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l1 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (CURRENT_DATE - '1 day'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l2 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (CURRENT_DATE - '30 days'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l3 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (CURRENT_DATE - '90 days'::interval) WITH DATA;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l4 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (date_trunc('month', CURRENT_DATE::timestamptz) - '6 mons'::interval)
|
||||
AND stat_date < date_trunc('month', CURRENT_DATE::timestamptz) WITH DATA;
|
||||
|
||||
-- 重建索引
|
||||
CREATE INDEX idx_mv_assistant_daily_l1 ON dws.mv_dws_assistant_daily_detail_l1 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX idx_mv_assistant_daily_l2 ON dws.mv_dws_assistant_daily_detail_l2 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX idx_mv_assistant_daily_l3 ON dws.mv_dws_assistant_daily_detail_l3 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX idx_mv_assistant_daily_l4 ON dws.mv_dws_assistant_daily_detail_l4 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX idx_mv_finance_daily_l1 ON dws.mv_dws_finance_daily_summary_l1 USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_mv_finance_daily_l2 ON dws.mv_dws_finance_daily_summary_l2 USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
### 4.1 确认 `biz_date()` 函数存在且行为正确
|
||||
|
||||
```sql
|
||||
-- 08:00 前归属前一天
|
||||
SELECT dws.biz_date('2026-01-15 07:59:59+08'::timestamptz) AS should_be_0114;
|
||||
-- 预期:2026-01-14
|
||||
|
||||
-- 08:00 起归属当天
|
||||
SELECT dws.biz_date('2026-01-15 08:00:00+08'::timestamptz) AS should_be_0115;
|
||||
-- 预期:2026-01-15
|
||||
|
||||
-- 月末边界
|
||||
SELECT dws.biz_date('2026-02-01 07:00:00+08'::timestamptz, 8) AS should_be_0131;
|
||||
-- 预期:2026-01-31
|
||||
```
|
||||
|
||||
### 4.2 确认 8 个物化视图已重建且定义包含 `biz_date`
|
||||
|
||||
```sql
|
||||
SELECT matviewname, definition LIKE '%biz_date%' AS uses_biz_date
|
||||
FROM pg_matviews
|
||||
WHERE schemaname = 'dws'
|
||||
AND matviewname LIKE 'mv_dws_%'
|
||||
ORDER BY matviewname;
|
||||
-- 预期:8 行,uses_biz_date 全部为 true
|
||||
```
|
||||
|
||||
### 4.3 确认物化视图索引完整
|
||||
|
||||
```sql
|
||||
SELECT indexname, tablename
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'dws'
|
||||
AND tablename LIKE 'mv_dws_%'
|
||||
ORDER BY indexname;
|
||||
-- 预期:8 个索引(assistant_daily l1-l4 + finance_daily l1-l4)
|
||||
```
|
||||
|
||||
### 4.4 确认物化视图有数据(刷新后)
|
||||
|
||||
```sql
|
||||
SELECT 'assistant_l1' AS mv, COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l1
|
||||
UNION ALL
|
||||
SELECT 'assistant_l2', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l2
|
||||
UNION ALL
|
||||
SELECT 'assistant_l3', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l3
|
||||
UNION ALL
|
||||
SELECT 'assistant_l4', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l4
|
||||
UNION ALL
|
||||
SELECT 'finance_l1', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l1
|
||||
UNION ALL
|
||||
SELECT 'finance_l2', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l2
|
||||
UNION ALL
|
||||
SELECT 'finance_l3', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l3
|
||||
UNION ALL
|
||||
SELECT 'finance_l4', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l4;
|
||||
```
|
||||
215
docs/database/BD_Manual_biz_tables.md
Normal file
215
docs/database/BD_Manual_biz_tables.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# BD_Manual:biz Schema 业务表(助教任务系统 + 备注系统 + 触发器调度)
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql`(建表)
|
||||
> - `db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql`(种子数据)
|
||||
> 关联 SPEC:`04-miniapp-core-business`(P4 小程序核心业务模块)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(4 张)
|
||||
|
||||
| # | 表名 | 用途 | 字段数 |
|
||||
|---|------|------|--------|
|
||||
| 1 | `biz.coach_tasks` | 助教任务表:存储任务分配、状态、有效期、置顶、放弃原因等 | 15 |
|
||||
| 2 | `biz.coach_task_history` | 任务变更历史表:记录任务关闭/新建/置顶/放弃的追溯链 | 9 |
|
||||
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 14 |
|
||||
| 4 | `biz.trigger_jobs` | 触发器配置表:存储 cron/interval/event 三种触发方式的配置与执行状态 | 9 |
|
||||
|
||||
### 表字段明细
|
||||
|
||||
#### biz.coach_tasks(15 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) |
|
||||
| `assistant_id` | BIGINT | NOT NULL | 助教 ID |
|
||||
| `member_id` | BIGINT | NOT NULL | 客户 ID |
|
||||
| `task_type` | VARCHAR(50) | NOT NULL | 任务类型:`high_priority_recall` / `priority_recall` / `follow_up_visit` / `relationship_building` |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'active' | 状态:`active` / `inactive` / `completed` / `abandoned` |
|
||||
| `priority_score` | NUMERIC(5,2) | 可空 | 优先级分数,取 `max(WBI, NCI)` 快照 |
|
||||
| `expires_at` | TIMESTAMPTZ | 可空 | 有效期时间戳,NULL 表示无限期 |
|
||||
| `is_pinned` | BOOLEAN | DEFAULT FALSE | 是否置顶 |
|
||||
| `abandon_reason` | TEXT | 可空 | 放弃原因(放弃时必填) |
|
||||
| `completed_at` | TIMESTAMPTZ | 可空 | 完成时间 |
|
||||
| `completed_task_type` | VARCHAR(50) | 可空 | 完成时的任务类型快照 |
|
||||
| `parent_task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 父任务 ID(自引用) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
#### biz.coach_task_history(9 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `task_id` | BIGINT | NOT NULL, FK → `biz.coach_tasks(id)` | 关联任务 |
|
||||
| `action` | VARCHAR(50) | NOT NULL | 操作类型:`created` / `type_changed` / `pinned` / `abandoned` / `cancel_abandon` / `expired` / `completed` |
|
||||
| `old_status` | VARCHAR(20) | 可空 | 变更前状态 |
|
||||
| `new_status` | VARCHAR(20) | 可空 | 变更后状态 |
|
||||
| `old_task_type` | VARCHAR(50) | 可空 | 变更前任务类型 |
|
||||
| `new_task_type` | VARCHAR(50) | 可空 | 变更后任务类型 |
|
||||
| `detail` | JSONB | 可空 | 附加详情(如放弃原因等) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 记录时间 |
|
||||
|
||||
#### biz.notes(14 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `user_id` | INTEGER | NOT NULL | 小程序用户 ID |
|
||||
| `target_type` | VARCHAR(50) | NOT NULL | 目标类型(如 `member`) |
|
||||
| `target_id` | BIGINT | NOT NULL | 目标 ID(如 member_id) |
|
||||
| `type` | VARCHAR(20) | NOT NULL DEFAULT 'normal' | 备注类型:`normal` / `follow_up` / `abandon_reason` |
|
||||
| `content` | TEXT | NOT NULL | 备注内容 |
|
||||
| `rating_service_willingness` | SMALLINT | CHECK (1-5),可空 | 再次服务意愿评分 |
|
||||
| `rating_revisit_likelihood` | SMALLINT | CHECK (1-5),可空 | 再来店可能性评分 |
|
||||
| `task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 关联任务 |
|
||||
| `ai_score` | SMALLINT | 可空 | AI 应用 6 评分(P5 实现) |
|
||||
| `ai_analysis` | TEXT | 可空 | AI 分析结果(P5 实现) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
#### biz.trigger_jobs(9 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | SERIAL | PK | 自增主键 |
|
||||
| `job_type` | VARCHAR(100) | NOT NULL | 任务类型标识,映射到 Python handler |
|
||||
| `job_name` | VARCHAR(100) | NOT NULL, UNIQUE | 任务名称(唯一) |
|
||||
| `trigger_condition` | VARCHAR(20) | NOT NULL | 触发方式:`cron` / `interval` / `event` |
|
||||
| `trigger_config` | JSONB | NOT NULL | 触发配置(cron 表达式 / 间隔秒数 / 事件名) |
|
||||
| `last_run_at` | TIMESTAMPTZ | 可空 | 上次运行时间 |
|
||||
| `next_run_at` | TIMESTAMPTZ | 可空 | 下次运行时间(event 类型为 NULL) |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'enabled' | 状态:`enabled` / `disabled` |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
|----|-----------|------|------|
|
||||
| `coach_tasks` | `idx_coach_tasks_site_assistant_member_type` | UNIQUE INDEX (partial) | `(site_id, assistant_id, member_id, task_type) WHERE status = 'active'`,保证同一组合下活跃任务最多一条 |
|
||||
| `coach_tasks` | `idx_coach_tasks_assistant_status` | INDEX | `(site_id, assistant_id, status)`,助教任务列表查询加速 |
|
||||
| `coach_tasks` | FK `parent_task_id` | FK | → `biz.coach_tasks(id)`,自引用 |
|
||||
| `coach_task_history` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
|
||||
| `notes` | `idx_notes_target` | INDEX | `(site_id, target_type, target_id)`,按目标查询备注加速 |
|
||||
| `notes` | CHECK `rating_service_willingness` | CHECK | `BETWEEN 1 AND 5` |
|
||||
| `notes` | CHECK `rating_revisit_likelihood` | CHECK | `BETWEEN 1 AND 5` |
|
||||
| `notes` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
|
||||
| `trigger_jobs` | UNIQUE `job_name` | UNIQUE | 触发器名称唯一 |
|
||||
|
||||
### 种子数据(4 条触发器配置)
|
||||
|
||||
| job_name | job_type | trigger_condition | trigger_config | next_run_at | 说明 |
|
||||
|----------|----------|-------------------|----------------|-------------|------|
|
||||
| `task_generator` | `task_generator` | `cron` | `{"cron_expression": "0 4 * * *"}` | 次日 04:00 | 每日凌晨 4:00 运行任务生成器 |
|
||||
| `task_expiry_check` | `task_expiry_check` | `interval` | `{"interval_seconds": 3600}` | NOW() + 1h | 每小时检查过期任务 |
|
||||
| `recall_completion_check` | `recall_completion_check` | `event` | `{"event_name": "etl_data_updated"}` | NULL | ETL 数据更新后触发召回完成检测 |
|
||||
| `note_reclassify_backfill` | `note_reclassify_backfill` | `event` | `{"event_name": "recall_completed"}` | NULL | 召回完成后触发备注回溯重分类 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无直接影响。`biz` Schema 表不参与 ETL 流程,但任务生成器通过 FDW 只读访问 ETL 库的 WBI/NCI/RS 指数数据 |
|
||||
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现任务 CRUD(`/api/xcx/tasks`)、备注 CRUD(`/api/xcx/notes`)、触发器调度等功能 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 API 间接使用任务列表、备注功能 |
|
||||
| 管理后台 | 暂无影响。后续可能增加任务监控和触发器管理界面 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `biz`,任务生成器和召回检测器通过 FDW 只读查询 ETL 库 |
|
||||
| `auth` Schema | 间接依赖。任务生成器通过 `auth.user_assistant_binding` 确定助教与小程序用户的映射关系 |
|
||||
| `public` Schema | 无影响。`member_retention_clue` 表独立于本次变更 |
|
||||
| 现有 `biz` Schema | 兼容。`biz` Schema 已由 P1 迁移脚本创建,本次仅在其中新增 4 张表,不修改已有对象 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
|
||||
|
||||
```sql
|
||||
-- 先删除种子数据(如需保留表结构)
|
||||
DELETE FROM biz.trigger_jobs
|
||||
WHERE job_name IN (
|
||||
'task_generator',
|
||||
'task_expiry_check',
|
||||
'recall_completion_check',
|
||||
'note_reclassify_backfill'
|
||||
);
|
||||
|
||||
-- 删除索引
|
||||
DROP INDEX IF EXISTS biz.idx_notes_target;
|
||||
DROP INDEX IF EXISTS biz.idx_coach_tasks_assistant_status;
|
||||
DROP INDEX IF EXISTS biz.idx_coach_tasks_site_assistant_member_type;
|
||||
|
||||
-- 删除表(按逆序,CASCADE 处理外键依赖)
|
||||
DROP TABLE IF EXISTS biz.trigger_jobs CASCADE;
|
||||
DROP TABLE IF EXISTS biz.notes CASCADE;
|
||||
DROP TABLE IF EXISTS biz.coach_task_history CASCADE;
|
||||
DROP TABLE IF EXISTS biz.coach_tasks CASCADE;
|
||||
```
|
||||
|
||||
注意:
|
||||
- `CASCADE` 会级联删除依赖对象(外键引用的子表数据)
|
||||
- 如果表中已有业务数据,需先备份再执行回滚
|
||||
- 回滚不会删除 `biz` Schema 本身(由 P1 创建,其他表可能依赖)
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 biz Schema 下 4 张业务表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'biz'
|
||||
AND table_name IN ('coach_tasks', 'coach_task_history', 'notes', 'trigger_jobs')
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 4 行(coach_task_history, coach_tasks, notes, trigger_jobs)
|
||||
|
||||
-- 2. 验证 coach_tasks 表字段数量和关键字段
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'coach_tasks'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 15 行,包含 id/site_id/assistant_id/member_id/task_type/status 等
|
||||
|
||||
-- 3. 验证部分唯一索引存在
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND indexname = 'idx_coach_tasks_site_assistant_member_type';
|
||||
-- 预期:返回 1 行,indexdef 包含 "WHERE ((status)::text = 'active'::text)"
|
||||
|
||||
-- 4. 验证 notes 表的 CHECK 约束(评分 1-5)
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.notes'::regclass AND contype = 'c';
|
||||
-- 预期:返回 2 行,分别约束 rating_service_willingness 和 rating_revisit_likelihood 在 1-5 范围
|
||||
|
||||
-- 5. 验证种子数据:4 条触发器配置
|
||||
SELECT job_name, job_type, trigger_condition,
|
||||
trigger_config->>'cron_expression' AS cron,
|
||||
trigger_config->>'interval_seconds' AS interval_sec,
|
||||
trigger_config->>'event_name' AS event
|
||||
FROM biz.trigger_jobs
|
||||
WHERE job_name IN ('task_generator', 'task_expiry_check', 'recall_completion_check', 'note_reclassify_backfill')
|
||||
ORDER BY job_name;
|
||||
-- 预期:返回 4 行
|
||||
-- note_reclassify_backfill | event | recall_completed
|
||||
-- recall_completion_check | event | etl_data_updated
|
||||
-- task_expiry_check | interval| interval_seconds=3600
|
||||
-- task_generator | cron | 0 4 * * *
|
||||
|
||||
-- 6. 验证查询索引存在
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz'
|
||||
AND indexname IN ('idx_coach_tasks_assistant_status', 'idx_notes_target')
|
||||
ORDER BY indexname;
|
||||
-- 预期:返回 2 行
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
# BD_Manual:dim_groupbuy_package_ex 新增团购详情字段
|
||||
|
||||
> 日期:2026-03-05
|
||||
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-03-05__add_detail_fields_to_dim_groupbuy_package_ex.sql`
|
||||
> 直接原因:整合团购详情接口(QueryPackageCouponInfo),需在 DWD 扩展表中存储可用台区、助教服务、关联门店等维度信息
|
||||
> Prompt 摘要:etl-coupon-detail spec — 需求 4 验收标准 1
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 变更内容
|
||||
|
||||
在 `dwd.dim_groupbuy_package_ex` 表新增 4 个 JSONB 列,用于存储从团购详情接口提取的维度数据。
|
||||
|
||||
| Schema | 表 | 新增列 | 类型 | 说明 |
|
||||
|--------|-----|--------|------|------|
|
||||
| dwd | dim_groupbuy_package_ex | table_area_ids | JSONB | 可用台区 ID 列表(来自详情接口 tableAreaId) |
|
||||
| dwd | dim_groupbuy_package_ex | table_area_names | JSONB | 可用台区名称列表(来自详情接口 tableAreaNameList) |
|
||||
| dwd | dim_groupbuy_package_ex | assistant_services | JSONB | 助教服务关联(来自详情接口 packageCouponAssistants) |
|
||||
| dwd | dim_groupbuy_package_ex | groupon_site_infos | JSONB | 关联门店信息(来自详情接口 grouponSiteInfos) |
|
||||
|
||||
所有列均为 NULLABLE,使用 `ADD COLUMN IF NOT EXISTS` 确保幂等性。
|
||||
|
||||
### 数据来源
|
||||
|
||||
ODS 层 `ods.group_buy_package_details` 表(由 `ODS_GROUP_PACKAGE` 任务的详情拉取子流程写入),通过 `coupon_id = groupbuy_package_id` 关联后在 DWD 加载时合并。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 | 说明 |
|
||||
|------|------|------|
|
||||
| ETL DWD 加载 | 需配合修改 | `dwd_load_task.py` 需新增 LEFT JOIN 逻辑从 ODS 详情表读取并映射到这 4 个字段(Task 4.2/4.3) |
|
||||
| ETL SCD2 | 自动兼容 | 新增 JSONB 字段自动纳入 `_is_row_changed` 变更检测 |
|
||||
| 后端 API | 无影响 | 当前无接口直接查询 dim_groupbuy_package_ex 的详情字段 |
|
||||
| 小程序 | 无影响 | 不直接使用 DWD 层表 |
|
||||
| RLS 视图 | 无影响 | dim_groupbuy_package_ex 无 RLS 视图 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
```sql
|
||||
ALTER TABLE dwd.dim_groupbuy_package_ex
|
||||
DROP COLUMN IF EXISTS table_area_ids,
|
||||
DROP COLUMN IF EXISTS table_area_names,
|
||||
DROP COLUMN IF EXISTS assistant_services,
|
||||
DROP COLUMN IF EXISTS groupon_site_infos;
|
||||
```
|
||||
|
||||
回滚后需同步撤销 `dwd_load_task.py` 中对应的 LEFT JOIN 和字段映射逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 4 个新列存在且类型正确
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd'
|
||||
AND table_name = 'dim_groupbuy_package_ex'
|
||||
AND column_name IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:4 行,data_type 均为 'jsonb',is_nullable 均为 'YES'
|
||||
|
||||
-- 2. 确认列 COMMENT 已写入
|
||||
SELECT a.attname AS column_name, d.description AS comment
|
||||
FROM pg_catalog.pg_attribute a
|
||||
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
||||
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
|
||||
LEFT JOIN pg_catalog.pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnum
|
||||
WHERE n.nspname = 'dwd'
|
||||
AND c.relname = 'dim_groupbuy_package_ex'
|
||||
AND a.attname IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
|
||||
ORDER BY a.attnum;
|
||||
-- 预期:4 行,每行 comment 非 NULL
|
||||
|
||||
-- 3. 确认表主键和索引未受影响
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'dwd'
|
||||
AND tablename = 'dim_groupbuy_package_ex';
|
||||
-- 预期:原有 4 个索引不变(pkey + 3 个辅助索引)
|
||||
```
|
||||
@@ -16,8 +16,8 @@
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `contribution_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `tenant_id` | INTEGER NOT NULL | — | 租户 ID | 飞球租户 ID |
|
||||
| `site_id` | BIGINT NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `tenant_id` | BIGINT NOT NULL | — | 租户 ID | 飞球租户 ID |
|
||||
| `assistant_id` | BIGINT NOT NULL | — | 助教 ID | 飞球助教 ID |
|
||||
| `assistant_nickname` | VARCHAR(100) | NULL | 助教昵称 | 中文昵称 |
|
||||
| `stat_date` | DATE NOT NULL | — | 统计日期 | `2025-01-15` |
|
||||
|
||||
94
docs/database/BD_Manual_fix_dim_staff_ex_rankname.md
Normal file
94
docs/database/BD_Manual_fix_dim_staff_ex_rankname.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# BD_Manual:修复 dim_staff_ex 列映射 rankname → rank_name
|
||||
|
||||
> 影响表:`dwd.dim_staff_ex`
|
||||
> ODS 源表:`ods.staff_info_master`
|
||||
> 修复日期:2026-02-26
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
|
||||
> 触发场景:`FLOW_API_FULL` 执行时 `DWD_LOAD_FROM_ODS` 阶段 dim_staff_ex 加载失败
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
DWD 加载任务 `DwdLoadTask` 中,`dwd.dim_staff_ex` 的列映射定义错误:
|
||||
|
||||
```python
|
||||
# 修复前(错误)
|
||||
("rank_name", "rankname", None)
|
||||
|
||||
# 修复后(正确)
|
||||
("rank_name", "rank_name", None)
|
||||
```
|
||||
|
||||
映射元组含义:`(dwd_列名, ods_源列名, 类型转换)`。
|
||||
|
||||
ODS 表 `ods.staff_info_master` 的实际列名为 `rank_name`(带下划线),而非 `rankname`。此错误导致 PostgreSQL 报 `UndefinedColumn: 字段 "rankname" 不存在`,dim_staff_ex 的 SCD2 合并在每个窗口段均失败(共 4 次)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL DWD 层 | dim_staff_ex 恢复正常加载,rank_name 字段将正确从 ODS 映射 |
|
||||
| DWS 层 | 无直接影响(当前无 DWS 任务依赖 dim_staff_ex.rank_name) |
|
||||
| 后端 API | 无影响(后端通过 FDW 读取,表结构未变) |
|
||||
| 小程序 | 无影响 |
|
||||
| DDL | 无变更,表结构不变 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
此修复仅涉及 Python 代码中的列映射字符串,无 DDL 变更。
|
||||
|
||||
回滚步骤:
|
||||
1. 将 `dwd_load_task.py` 中 dim_staff_ex 映射恢复为 `("rank_name", "rankname", None)`
|
||||
2. 注意:回滚后 dim_staff_ex 将再次无法加载 rank_name 字段
|
||||
|
||||
已加载的数据无需回滚——修复前该字段从未成功写入。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 验证 1:确认 ODS 源表列名为 rank_name(非 rankname)
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'ods'
|
||||
AND table_name = 'staff_info_master'
|
||||
AND column_name IN ('rank_name', 'rankname');
|
||||
-- 预期:仅返回 rank_name
|
||||
|
||||
-- 验证 2:确认 DWD 目标表存在 rank_name 列
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd'
|
||||
AND table_name = 'dim_staff_ex'
|
||||
AND column_name = 'rank_name';
|
||||
-- 预期:返回 1 行
|
||||
|
||||
-- 验证 3:修复后重跑 ETL,检查 dim_staff_ex 是否有 rank_name 非空数据
|
||||
SELECT COUNT(*) AS total,
|
||||
COUNT(rank_name) AS has_rank_name
|
||||
FROM dwd.dim_staff_ex
|
||||
WHERE scd2_is_current = 1;
|
||||
-- 预期:has_rank_name > 0(取决于上游数据是否有值)
|
||||
|
||||
-- 验证 4:对比 ODS 与 DWD 的 rank_name 一致性
|
||||
SELECT s.id AS staff_id, s.rank_name AS ods_rank_name, d.rank_name AS dwd_rank_name
|
||||
FROM ods.staff_info_master s
|
||||
JOIN dwd.dim_staff_ex d ON s.id = d.staff_id AND d.scd2_is_current = 1
|
||||
WHERE s.rank_name IS DISTINCT FROM d.rank_name
|
||||
LIMIT 10;
|
||||
-- 预期:修复并重跑后返回 0 行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 映射修正记录
|
||||
|
||||
| 日期 | 字段 | 修正内容 |
|
||||
|------|------|---------|
|
||||
| 2026-02-26 | `rank_name` | ODS 源列名从 `rankname` 修正为 `rank_name`,与 `ods.staff_info_master` DDL 一致 |
|
||||
132
docs/database/BD_Manual_fix_dws_assistant_daily_table_area.md
Normal file
132
docs/database/BD_Manual_fix_dws_assistant_daily_table_area.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# BD_Manual:修复 DWS_ASSISTANT_DAILY 缺失 table_area_name 列
|
||||
|
||||
> 影响任务:`DWS_ASSISTANT_DAILY`
|
||||
> 涉及表:`dwd.dwd_assistant_service_log`(读取)、`dwd.dim_table`(新增 JOIN)、`dws.dws_assistant_daily`(写入)
|
||||
> 修复日期:2026-02-26
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` → `_extract_service_records()`
|
||||
> 触发场景:`FLOW_API_FULL` 执行时 DWS 阶段首个任务失败,级联导致后续 13 个 DWS 任务全部 `InFailedSqlTransaction`
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
`_extract_service_records()` 方法的 SQL 原先直接从 `dwd.dwd_assistant_service_log` 读取 `asl.table_area_name`,但该表实际不存在此列(DDL 可确认)。
|
||||
|
||||
修复方式:通过 LEFT JOIN `dwd.dim_table` 获取台区名称。
|
||||
|
||||
```sql
|
||||
-- 修复前(错误)
|
||||
SELECT ...
|
||||
asl.table_area_name,
|
||||
...
|
||||
FROM dwd.dwd_assistant_service_log asl
|
||||
LEFT JOIN dwd.dwd_assistant_service_log_ex ex ...
|
||||
|
||||
-- 修复后(正确)
|
||||
SELECT ...
|
||||
COALESCE(dt.site_table_area_name, '') AS table_area_name,
|
||||
...
|
||||
FROM dwd.dwd_assistant_service_log asl
|
||||
LEFT JOIN dwd.dwd_assistant_service_log_ex ex ...
|
||||
LEFT JOIN dwd.dim_table dt
|
||||
ON asl.site_table_id = dt.table_id
|
||||
AND dt.scd2_is_current = 1
|
||||
```
|
||||
|
||||
JOIN 条件说明:
|
||||
- `asl.site_table_id = dt.table_id`:通过台桌 ID 关联维度表
|
||||
- `dt.scd2_is_current = 1`:仅取当前有效的 SCD2 版本
|
||||
- `COALESCE(..., '')`:dim_table 无匹配时回退空字符串,避免 NULL 传播
|
||||
|
||||
---
|
||||
|
||||
## 2. 级联失败说明
|
||||
|
||||
此 bug 不仅导致 `DWS_ASSISTANT_DAILY` 本身失败,还因 DWS 阶段共享同一数据库连接且无逐任务 rollback,使 psycopg2 进入 `InFailedSqlTransaction` 状态,后续 13 个 DWS/INDEX 任务全部失败:
|
||||
|
||||
- DWS_ASSISTANT_CUSTOMER, DWS_ASSISTANT_SALARY, DWS_ASSISTANT_FINANCE, DWS_ASSISTANT_MONTHLY
|
||||
- DWS_MEMBER_CONSUMPTION, DWS_MEMBER_VISIT
|
||||
- DWS_FINANCE_DAILY, DWS_FINANCE_RECHARGE, DWS_FINANCE_INCOME_STRUCTURE, DWS_FINANCE_DISCOUNT_DETAIL
|
||||
- DWS_WINBACK_INDEX, DWS_NEWCONV_INDEX, DWS_RELATION_INDEX
|
||||
|
||||
修复此根因后,上述任务均可恢复正常执行。
|
||||
|
||||
---
|
||||
|
||||
## 3. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL DWS 层 | `dws_assistant_daily` 恢复正常写入,`table_area_name` 来源从不存在的列改为 dim_table 维度表 |
|
||||
| 后续 DWS 任务 | 级联失败消除,所有 DWS 任务可正常执行 |
|
||||
| 后端 API | 无影响(DWS 聚合表结构未变) |
|
||||
| 管理后台 | 助教日报表将正确显示台区名称 |
|
||||
| DDL | 无变更,无新增表/列 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 回滚策略
|
||||
|
||||
此修复仅涉及 Python 代码中的 SQL 查询,无 DDL 变更。
|
||||
|
||||
回滚步骤:
|
||||
1. 将 `assistant_daily_task.py` 中 `_extract_service_records()` 的 SQL 恢复为 `asl.table_area_name`,移除 `LEFT JOIN dwd.dim_table`
|
||||
2. 注意:回滚后 DWS_ASSISTANT_DAILY 将再次失败
|
||||
|
||||
已写入的 DWS 数据如需回滚:
|
||||
```sql
|
||||
-- 清除修复后写入的 dws_assistant_daily 数据(按需执行)
|
||||
DELETE FROM dws.dws_assistant_daily
|
||||
WHERE stat_date >= '2025-11-01';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 验证 1:确认 dwd_assistant_service_log 确实没有 table_area_name 列
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd'
|
||||
AND table_name = 'dwd_assistant_service_log'
|
||||
AND column_name = 'table_area_name';
|
||||
-- 预期:返回 0 行
|
||||
|
||||
-- 验证 2:确认 dim_table 存在 site_table_area_name 列
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd'
|
||||
AND table_name = 'dim_table'
|
||||
AND column_name = 'site_table_area_name';
|
||||
-- 预期:返回 1 行
|
||||
|
||||
-- 验证 3:检查 JOIN 关联覆盖率(service_log 的 site_table_id 在 dim_table 中的匹配率)
|
||||
SELECT
|
||||
COUNT(*) AS total_records,
|
||||
COUNT(dt.table_id) AS matched_dim_table,
|
||||
ROUND(COUNT(dt.table_id)::numeric / NULLIF(COUNT(*), 0) * 100, 1) AS match_pct
|
||||
FROM dwd.dwd_assistant_service_log asl
|
||||
LEFT JOIN dwd.dim_table dt
|
||||
ON asl.site_table_id = dt.table_id
|
||||
AND dt.scd2_is_current = 1
|
||||
WHERE asl.is_delete = 0;
|
||||
-- 预期:match_pct 接近 100%
|
||||
|
||||
-- 验证 4:修复后重跑 ETL,检查 dws_assistant_daily 是否有数据
|
||||
SELECT stat_date, COUNT(*) AS rows
|
||||
FROM dws.dws_assistant_daily
|
||||
WHERE stat_date >= '2025-11-01'
|
||||
GROUP BY stat_date
|
||||
ORDER BY stat_date
|
||||
LIMIT 10;
|
||||
-- 预期:有数据行返回
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码引用
|
||||
|
||||
- 修复文件:`apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` → `_extract_service_records()`
|
||||
- 关联 BD Manual:`BD_Manual_assistant_service_records.md`(dwd_assistant_service_log 字段映射文档)
|
||||
- dim_table DDL:`docs/database/ddl/etl_feiqiu__dwd.sql`(含 `site_table_area_name` 列定义)
|
||||
92
docs/database/BD_Manual_group_buy_package_details.md
Normal file
92
docs/database/BD_Manual_group_buy_package_details.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# BD_Manual:ods.group_buy_package_details 团购套餐详情表
|
||||
|
||||
> 日期:2026-03-05
|
||||
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
|
||||
> DDL 路径:`db/etl_feiqiu/ods/group_buy_package_details.sql`
|
||||
> 直接原因:整合团购详情接口(QueryPackageCouponInfo),新建 ODS 详情表存储每个团购套餐的详情数据
|
||||
> Prompt 摘要:etl-coupon-detail spec — 需求 3 验收标准 1-4
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 变更内容
|
||||
|
||||
新建 `ods.group_buy_package_details` 表,用于存储 `QueryPackageCouponInfo` 详情接口的原始数据。
|
||||
|
||||
| Schema | 表 | 操作 | 说明 |
|
||||
|--------|-----|------|------|
|
||||
| ods | group_buy_package_details | 新建 | 团购套餐详情,主键 `coupon_id`,含 12 个结构化字段 + 6 个 JSONB 数组字段 + 3 个 ETL 元数据字段 |
|
||||
|
||||
### 数据获取方式
|
||||
|
||||
通过 `ODS_GROUP_PACKAGE` 任务的 `detail_endpoint` 二级详情拉取子流程:
|
||||
- 主流程拉取团购列表 → `ods.group_buy_packages`
|
||||
- 子流程遍历每个 `id`,串行调用 `QueryPackageCouponInfo` → 本表
|
||||
- 全量快照模式,UPSERT on `coupon_id`
|
||||
|
||||
### 关键字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `coupon_id` | BIGINT PK | 团购套餐 ID(= group_buy_packages.id) |
|
||||
| `table_area_ids` | JSONB | 可用台区 ID 列表 |
|
||||
| `table_area_names` | JSONB | 可用台区名称列表 |
|
||||
| `assistant_services` | JSONB | 助教服务关联数组 |
|
||||
| `groupon_site_infos` | JSONB | 关联门店信息数组 |
|
||||
| `package_services` | JSONB | 套餐服务数组(待调研) |
|
||||
| `coupon_details_list` | JSONB | 券明细数组(待调研) |
|
||||
| `content_hash` | TEXT | 内容哈希,用于变更检测 |
|
||||
| `payload` | JSONB | 完整原始 JSON 响应 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 | 说明 |
|
||||
|------|------|------|
|
||||
| ETL ODS 层 | 新增表 | `ODS_GROUP_PACKAGE` 任务通过 `detail_endpoint` 配置自动写入 |
|
||||
| ETL DWD 层 | 需配合修改 | `dwd_load_task.py` 需 LEFT JOIN 本表将 4 个 JSONB 字段合并到 `dim_groupbuy_package_ex` |
|
||||
| 后端 API | 无影响 | 当前无接口直接查询本表 |
|
||||
| 小程序 | 无影响 | 不直接使用 ODS 层表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS ods.group_buy_package_details;
|
||||
```
|
||||
|
||||
回滚后需同步移除 `ODS_GROUP_PACKAGE` 任务中的 `detail_endpoint` 相关配置。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认表存在且主键正确
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'ods'
|
||||
AND table_name = 'group_buy_package_details'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:22 列,coupon_id 为 BIGINT NOT NULL
|
||||
|
||||
-- 2. 确认主键约束
|
||||
SELECT constraint_name, constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = 'ods'
|
||||
AND table_name = 'group_buy_package_details'
|
||||
AND constraint_type = 'PRIMARY KEY';
|
||||
-- 预期:1 行,pk_group_buy_package_details
|
||||
|
||||
-- 3. 确认 JSONB 列存在
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'ods'
|
||||
AND table_name = 'group_buy_package_details'
|
||||
AND data_type = 'jsonb'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:8 行(table_area_ids, table_area_names, assistant_services, groupon_site_infos, package_services, coupon_details_list, payload)
|
||||
```
|
||||
159
docs/database/BD_Manual_member_retention_clue.md
Normal file
159
docs/database/BD_Manual_member_retention_clue.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# BD 手册:维客线索表(member_retention_clue)
|
||||
|
||||
## 概述
|
||||
|
||||
在 `zqyy_app` / `test_zqyy_app` 业务库中新建 `member_retention_clue` 表,替代原 `member_birthday_manual` 表。维客线索是助教为会员记录的销售/维护线索,采用"大类 + 摘要 + 详情"三层结构,覆盖客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈六个维度。
|
||||
|
||||
生日信息不再单独建表,作为"客户基础"大类下的一条线索记录。
|
||||
|
||||
## 变更说明
|
||||
|
||||
| 库 | Schema | 表 | 变更类型 | 说明 |
|
||||
|----|--------|---|---------|------|
|
||||
| zqyy_app / test_zqyy_app | public | member_birthday_manual | 删除 | 旧表,生日单独记录方案废弃 |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue | 新建 | 维客线索表 |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue.source | 新增列 | 2026-02-27 补齐线索来源字段 |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue.category | 约束变更 | 2026-03-08 枚举对齐:`客户基础信息` → `客户基础` |
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| member_id | BIGINT | NOT NULL | 会员 ID |
|
||||
| category | VARCHAR(20) | NOT NULL, CHECK | 线索大类枚举(6 值,见下方枚举表) |
|
||||
| summary | VARCHAR(200) | NOT NULL | 摘要:重点信息 |
|
||||
| detail | TEXT | 可为空 | 详情:分析及扩展说明 |
|
||||
| recorded_by_assistant_id | BIGINT | — | 记录助教 ID |
|
||||
| recorded_by_name | VARCHAR(50) | — | 记录助教姓名 |
|
||||
| recorded_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录时间 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID(多门店隔离) |
|
||||
| source | VARCHAR(20) | NOT NULL DEFAULT 'manual' | 线索来源(2026-02-27 新增) |
|
||||
|
||||
### category 枚举值
|
||||
|
||||
> 2026-03-08 枚举对齐:`客户基础信息` → `客户基础`(P5 spec 评审决定,与 AI 应用 Prompt 统一)
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| 客户基础 | 生日、职业、偏好时段等基础画像 |
|
||||
| 消费习惯 | 消费频次、客单价、消费时段等 |
|
||||
| 玩法偏好 | 中式/斯诺克/美式偏好、技术水平等 |
|
||||
| 促销偏好 | 对储值活动、套餐、折扣的敏感度 |
|
||||
| 社交关系 | 常带朋友、固定球搭子、社交圈等 |
|
||||
| 重要反馈 | 客户提出的需求、投诉、建议等 |
|
||||
|
||||
### source 枚举值(2026-02-27 新增)
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| manual | 助教手动录入(默认值) |
|
||||
| ai_consumption | 应用 3:消费分析自动生成 |
|
||||
| ai_note | 应用 6:备注分析自动提取 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| member_retention_clue_pkey | PRIMARY KEY | id | 主键 |
|
||||
| chk_retention_clue_category | CHECK | category | 限制大类枚举值 |
|
||||
| idx_retention_clue_member | INDEX (btree) | member_id | 按会员查询 |
|
||||
| idx_retention_clue_site | INDEX (btree) | site_id | 按门店查询 |
|
||||
| idx_retention_clue_category | INDEX (btree) | (member_id, category) | 按会员+大类查询 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
- **后端 API**:`POST /api/member-birthday` 废弃,替换为 `POST /api/retention-clue`、`GET /api/retention-clue/{member_id}`、`DELETE /api/retention-clue/{clue_id}`
|
||||
- **source 字段**(2026-02-27):`POST /api/retention-clue` 接受可选 `source` 参数,默认 `manual`;`GET` 返回中包含 `source` 字段。已有数据自动填充 `DEFAULT 'manual'`,向后兼容
|
||||
- **ETL Connector**:DWS 任务移除 FDW 读取 `member_birthday_manual` 的逻辑,生日仅从 `dim_member.birthday`(API 来源)读取
|
||||
- **FDW**:`fdw_app.member_birthday_manual` 外部表需在 ETL 库侧同步更新为 `fdw_app.member_retention_clue`(含 `source` 列)
|
||||
- **小程序**:助教端调用新 API 提交维客线索
|
||||
- **H5 原型**:customer-detail 和 task-detail 页面"消费习惯"板块改为"维客线索"
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### 仅回滚 source 列(2026-02-27 变更)
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
ALTER TABLE member_retention_clue DROP COLUMN IF EXISTS source;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 完整回滚(整表)
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
DROP TABLE IF EXISTS member_retention_clue CASCADE;
|
||||
-- 如需恢复旧表,执行归档的 2026-02-22__C2_member_birthday_manual.sql
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
## 验证步骤
|
||||
|
||||
```sql
|
||||
-- 1. 确认旧表已删除
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'member_birthday_manual';
|
||||
-- 预期:0 行
|
||||
|
||||
-- 2. 确认新表存在
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认列结构完整(10 列)
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:id, member_id, category, summary, detail,
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id, source
|
||||
|
||||
-- 4. 确认 CHECK 约束
|
||||
SELECT conname FROM pg_constraint
|
||||
WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
|
||||
-- 预期:chk_retention_clue_category
|
||||
|
||||
-- 5. 确认索引(3 + 主键)
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE tablename = 'member_retention_clue';
|
||||
-- 预期:4 行
|
||||
|
||||
-- 6. 确认表注释
|
||||
SELECT obj_description('member_retention_clue'::regclass, 'pg_class');
|
||||
-- 预期:包含"维客线索"
|
||||
|
||||
-- 7. 确认 source 列存在且默认值正确(2026-02-27)
|
||||
SELECT column_name, data_type, column_default, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'member_retention_clue'
|
||||
AND column_name = 'source';
|
||||
-- 预期:1 行,varchar, 'manual'::character varying, NO
|
||||
|
||||
-- 8. 确认 source 列注释
|
||||
SELECT col_description(
|
||||
(SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
|
||||
(SELECT ordinal_position FROM information_schema.columns
|
||||
WHERE table_name = 'member_retention_clue' AND column_name = 'source')
|
||||
);
|
||||
-- 预期:包含 'manual' / 'ai_consumption' / 'ai_note'
|
||||
|
||||
-- 9. 确认已有数据的 source 分布
|
||||
SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
|
||||
-- 预期:全部为 'manual'(或空表)
|
||||
```
|
||||
|
||||
## 关联文件
|
||||
|
||||
- 迁移脚本(建表):`db/zqyy_app/migrations/2026-02-26__refactor_birthday_to_retention_clue.sql`
|
||||
- 迁移脚本(source 列):`db/zqyy_app/migrations/2026-02-27__add_source_to_retention_clue.sql`
|
||||
- 迁移脚本(category 枚举对齐):`db/zqyy_app/migrations/2026-03-08__align_retention_clue_category_enum.sql`
|
||||
- FDW 反向映射(生产):`db/fdw/setup_fdw_reverse.sql`
|
||||
- FDW 反向映射(测试):`db/fdw/setup_fdw_reverse_test.sql`
|
||||
- 后端路由:`apps/backend/app/routers/member_retention_clue.py`
|
||||
- 后端模型:`apps/backend/app/schemas/member_retention_clue.py`
|
||||
- H5 原型:`docs/h5_ui/pages/customer-detail.html`、`docs/h5_ui/pages/task-detail.html`
|
||||
- 旧表文档(已归档):`docs/database/_archived/BD_Manual_member_birthday_manual.md`
|
||||
- 旧 FDW 文档(已归档):`docs/database/_archived/BD_Manual_fdw_reverse_member_birthday.md`
|
||||
128
docs/database/BD_Manual_tenant_id_int_to_bigint.md
Normal file
128
docs/database/BD_Manual_tenant_id_int_to_bigint.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# BD_Manual:tenant_id INTEGER → BIGINT 迁移
|
||||
|
||||
> 日期:2026-03-03
|
||||
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`、`zqyy_app` / `test_zqyy_app`
|
||||
> 迁移脚本:
|
||||
> - `db/etl_feiqiu/migrations/2026-03-03__alter_tenant_id_int_to_bigint.sql`
|
||||
> - `db/zqyy_app/migrations/2026-03-03__alter_tenant_id_int_to_bigint.sql`
|
||||
> 直接原因:飞球 tenant_id(如 2790683160709957)远超 int4 上限(2,147,483,647),导致写入溢出
|
||||
> Prompt 摘要:修复 tenant_id int4 溢出问题,迁移为 bigint
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 变更前
|
||||
|
||||
| 库 | Schema | 表 | 列 | 类型 |
|
||||
|----|--------|----|----|------|
|
||||
| etl_feiqiu | dws | dws_assistant_order_contribution | tenant_id | INTEGER (int4) NOT NULL |
|
||||
| zqyy_app | auth | site_code_mapping | tenant_id | INTEGER (int4) NULL |
|
||||
|
||||
### 变更后
|
||||
|
||||
| 库 | Schema | 表 | 列 | 类型 |
|
||||
|----|--------|----|----|------|
|
||||
| etl_feiqiu | dws | dws_assistant_order_contribution | tenant_id | BIGINT (int8) NOT NULL |
|
||||
| zqyy_app | auth | site_code_mapping | tenant_id | BIGINT (int8) NULL |
|
||||
|
||||
### 级联影响
|
||||
|
||||
| 对象 | 类型 | 处理方式 |
|
||||
|------|------|---------|
|
||||
| `app.v_dws_assistant_order_contribution` (ETL 库) | RLS 视图 | DROP → ALTER → 重建(SELECT *) |
|
||||
| `fdw_etl.v_dws_assistant_order_contribution` (App 库) | FDW 外部表 | DROP → IMPORT FOREIGN SCHEMA 重新导入 |
|
||||
| `app.v_dws_assistant_order_contribution` (App 库) | RLS 视图 | 自动继承 FDW 外部表类型 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 | 说明 |
|
||||
|------|------|------|
|
||||
| ETL 任务 | 无影响 | `assistant_order_contribution_task.py` 从 DWD 层读取 tenant_id(已是 bigint),写入 DWS 现在类型匹配 |
|
||||
| 后端 API | 无影响 | 通过 FDW 视图读取,类型自动跟随源表 |
|
||||
| 小程序 | 无影响 | 不直接使用 tenant_id |
|
||||
| `init_test_user.py` | 已更新 | 移除 `_safe_tenant_id()` 中的 int4 范围检查降级逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
### ETL 库回滚
|
||||
```sql
|
||||
BEGIN;
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
|
||||
ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN tenant_id TYPE integer;
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
SELECT * FROM dws.dws_assistant_order_contribution
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### App 库回滚
|
||||
```sql
|
||||
BEGIN;
|
||||
ALTER TABLE auth.site_code_mapping ALTER COLUMN tenant_id TYPE integer;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 代码回滚
|
||||
恢复 `scripts/ops/init_test_user.py` 中 `_safe_tenant_id()` 的 int4 范围检查逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
### ETL 库(test_etl_feiqiu)
|
||||
|
||||
```sql
|
||||
-- 1. 确认 dws 表 tenant_id 类型
|
||||
SELECT column_name, data_type, udt_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dws'
|
||||
AND table_name = 'dws_assistant_order_contribution'
|
||||
AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint', udt_name = 'int8'
|
||||
|
||||
-- 2. 确认 RLS 视图存在且类型正确
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'app'
|
||||
AND table_name = 'v_dws_assistant_order_contribution'
|
||||
AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint'
|
||||
|
||||
-- 3. 确认全库无残留 int4 tenant_id
|
||||
SELECT table_schema, table_name
|
||||
FROM information_schema.columns
|
||||
WHERE column_name = 'tenant_id' AND udt_name = 'int4';
|
||||
-- 预期:0 行
|
||||
```
|
||||
|
||||
### App 库(test_zqyy_app)
|
||||
|
||||
```sql
|
||||
-- 1. 确认 auth 表 tenant_id 类型
|
||||
SELECT column_name, data_type, udt_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name = 'site_code_mapping'
|
||||
AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint', udt_name = 'int8'
|
||||
|
||||
-- 2. 确认 FDW 外部表类型正确
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'fdw_etl'
|
||||
AND table_name = 'v_dws_assistant_order_contribution'
|
||||
AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint'
|
||||
|
||||
-- 3. 确认全库无残留 int4 tenant_id
|
||||
SELECT table_schema, table_name
|
||||
FROM information_schema.columns
|
||||
WHERE column_name = 'tenant_id' AND udt_name = 'int4';
|
||||
-- 预期:0 行
|
||||
```
|
||||
@@ -14,6 +14,7 @@
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(43 视图,无表) |
|
||||
| `zqyy_app__public.sql` | zqyy_app | public | 小程序业务表(12 表) |
|
||||
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限(8 表) |
|
||||
| `zqyy_app__biz.sql` | zqyy_app | biz | 核心业务表(任务/备注/触发器,4 表) |
|
||||
| `fdw.sql` | — | — | FDW 正向跨库映射配置(etl→app) |
|
||||
|
||||
## 数据字典(BD_Manual — ODS→DWD 字段映射)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / app(RLS 视图层)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -322,6 +322,9 @@ SELECT recharge_order_id,
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
-- ⚠️ consume_money 透传自 DWD 层,存在三种历史口径(A/B/C),API 消费端不应直接展示或参与计算。
|
||||
-- 应使用 items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money
|
||||
-- settle_type 枚举:1=台桌结账, 3=商城订单, 6=退货订单, 7=退款订单(本表无 is_delete 字段)
|
||||
CREATE OR REPLACE VIEW app.v_dwd_settlement_head AS
|
||||
SELECT order_settle_id,
|
||||
tenant_id,
|
||||
@@ -683,7 +686,7 @@ SELECT id,
|
||||
platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total,
|
||||
cash_card_consume,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
@@ -992,7 +995,8 @@ SELECT id,
|
||||
total_discount,
|
||||
actual_pay,
|
||||
cash_pay,
|
||||
cash_card_pay,
|
||||
balance_pay,
|
||||
recharge_card_pay,
|
||||
gift_card_pay,
|
||||
groupbuy_pay,
|
||||
table_duration_min,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / core(跨门店标准化维度/事实)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dwd(明细数据层)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -616,27 +616,6 @@ CREATE TABLE dwd.dwd_assistant_service_log_ex (
|
||||
operator_name text
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_assistant_trash_event (
|
||||
assistant_trash_event_id bigint NOT NULL,
|
||||
site_id bigint,
|
||||
table_id bigint,
|
||||
table_area_id bigint,
|
||||
assistant_no character varying(32),
|
||||
assistant_name character varying(64),
|
||||
charge_minutes_raw integer,
|
||||
abolish_amount numeric(18,2),
|
||||
trash_reason character varying(255),
|
||||
create_time timestamp with time zone,
|
||||
tenant_id bigint
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_assistant_trash_event_ex (
|
||||
assistant_trash_event_id bigint NOT NULL,
|
||||
table_name character varying(64),
|
||||
table_area_name character varying(64),
|
||||
assistant_no_int integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_goods_stock_movement (
|
||||
site_goods_stock_id bigint NOT NULL,
|
||||
tenant_id bigint,
|
||||
@@ -1170,8 +1149,6 @@ ALTER TABLE dwd.dim_tenant_goods ADD CONSTRAINT dim_tenant_goods_pkey PRIMARY KE
|
||||
ALTER TABLE dwd.dim_tenant_goods_ex ADD CONSTRAINT dim_tenant_goods_ex_pkey PRIMARY KEY (tenant_goods_id, scd2_start_time);
|
||||
ALTER TABLE dwd.dwd_assistant_service_log ADD CONSTRAINT dwd_assistant_service_log_pkey PRIMARY KEY (assistant_service_id);
|
||||
ALTER TABLE dwd.dwd_assistant_service_log_ex ADD CONSTRAINT dwd_assistant_service_log_ex_pkey PRIMARY KEY (assistant_service_id);
|
||||
ALTER TABLE dwd.dwd_assistant_trash_event ADD CONSTRAINT dwd_assistant_trash_event_pkey PRIMARY KEY (assistant_trash_event_id);
|
||||
ALTER TABLE dwd.dwd_assistant_trash_event_ex ADD CONSTRAINT dwd_assistant_trash_event_ex_pkey PRIMARY KEY (assistant_trash_event_id);
|
||||
ALTER TABLE dwd.dwd_goods_stock_movement ADD CONSTRAINT dwd_goods_stock_movement_pkey PRIMARY KEY (site_goods_stock_id);
|
||||
ALTER TABLE dwd.dwd_goods_stock_summary ADD CONSTRAINT dwd_goods_stock_summary_pkey PRIMARY KEY (site_goods_id, fetched_at);
|
||||
ALTER TABLE dwd.dwd_groupbuy_redemption ADD CONSTRAINT dwd_groupbuy_redemption_pkey PRIMARY KEY (redemption_id);
|
||||
@@ -1262,8 +1239,6 @@ CREATE INDEX idx_dwd_assistant_service_log_time_create_time ON dwd.dwd_assistant
|
||||
CREATE INDEX idx_dwd_assistant_service_log_time_pk_118fd0d3 ON dwd.dwd_assistant_service_log USING btree (create_time, assistant_service_id);
|
||||
CREATE INDEX idx_dwd_assistant_service_log_time_pk_3fb2dede ON dwd.dwd_assistant_service_log USING btree (start_use_time, assistant_service_id);
|
||||
CREATE INDEX idx_dwd_assistant_service_log_time_start_use_time ON dwd.dwd_assistant_service_log USING btree (start_use_time);
|
||||
CREATE INDEX idx_dwd_assistant_trash_event_time_create_time ON dwd.dwd_assistant_trash_event USING btree (create_time);
|
||||
CREATE INDEX idx_dwd_assistant_trash_event_time_pk_0b64af2a ON dwd.dwd_assistant_trash_event USING btree (create_time, assistant_trash_event_id);
|
||||
CREATE INDEX idx_dwd_groupbuy_redemption_time_create_time ON dwd.dwd_groupbuy_redemption USING btree (create_time);
|
||||
CREATE INDEX idx_dwd_groupbuy_redemption_time_pk_create_time_redemption_id ON dwd.dwd_groupbuy_redemption USING btree (create_time, redemption_id);
|
||||
CREATE INDEX idx_dwd_payment_time_create_time ON dwd.dwd_payment USING btree (create_time);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dws(汇总数据层)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -42,8 +42,11 @@ CREATE SEQUENCE IF NOT EXISTS dws.dws_platform_settlement_id_seq AS bigint;
|
||||
CREATE TABLE dws.cfg_area_category (
|
||||
category_id integer DEFAULT nextval('dws.cfg_area_category_category_id_seq'::regclass) NOT NULL,
|
||||
source_area_name character varying(100) NOT NULL,
|
||||
source_table_name character varying(100) DEFAULT NULL,
|
||||
category_code character varying(20) NOT NULL,
|
||||
category_name character varying(50) NOT NULL,
|
||||
display_name character varying(50) DEFAULT NULL,
|
||||
short_name character varying(20) DEFAULT NULL,
|
||||
match_type character varying(10) DEFAULT 'EXACT'::character varying NOT NULL,
|
||||
match_priority integer DEFAULT 100 NOT NULL,
|
||||
is_active boolean DEFAULT true NOT NULL,
|
||||
@@ -51,6 +54,9 @@ CREATE TABLE dws.cfg_area_category (
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
-- 唯一约束:(source_area_name, source_table_name) 支持同一台区下按台桌细分
|
||||
CREATE UNIQUE INDEX uk_cfg_area_category
|
||||
ON dws.cfg_area_category (source_area_name, COALESCE(source_table_name, ''));
|
||||
|
||||
CREATE TABLE dws.cfg_assistant_level_price (
|
||||
price_id integer DEFAULT nextval('dws.cfg_assistant_level_price_price_id_seq'::regclass) NOT NULL,
|
||||
@@ -102,7 +108,7 @@ CREATE TABLE dws.cfg_performance_tier (
|
||||
min_hours numeric(10,2) NOT NULL,
|
||||
max_hours numeric(10,2),
|
||||
base_deduction numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
bonus_deduction_ratio numeric(5,4) DEFAULT 0 NOT NULL,
|
||||
bonus_deduction_ratio numeric(7,4) DEFAULT 0 NOT NULL,
|
||||
vacation_days integer DEFAULT 0 NOT NULL,
|
||||
vacation_unlimited boolean DEFAULT false NOT NULL,
|
||||
is_new_hire_tier boolean DEFAULT false NOT NULL,
|
||||
@@ -215,7 +221,7 @@ CREATE TABLE dws.dws_assistant_finance_analysis (
|
||||
revenue_room numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cost_daily numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gross_profit numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gross_margin numeric(5,4) DEFAULT 0 NOT NULL,
|
||||
gross_margin numeric(7,4) DEFAULT 0 NOT NULL,
|
||||
service_count integer DEFAULT 0 NOT NULL,
|
||||
service_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
room_service_count integer DEFAULT 0 NOT NULL,
|
||||
@@ -265,8 +271,8 @@ CREATE TABLE dws.dws_assistant_monthly_summary (
|
||||
|
||||
CREATE TABLE dws.dws_assistant_order_contribution (
|
||||
contribution_id bigint DEFAULT nextval('dws.dws_assistant_order_contribution_contribution_id_seq'::regclass) NOT NULL,
|
||||
site_id integer NOT NULL,
|
||||
tenant_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
assistant_nickname character varying(100),
|
||||
stat_date date NOT NULL,
|
||||
@@ -291,7 +297,7 @@ CREATE TABLE dws.dws_assistant_recharge_commission (
|
||||
recharge_order_no character varying(50),
|
||||
recharge_amount numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
commission_amount numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
commission_ratio numeric(5,4),
|
||||
commission_ratio numeric(7,4),
|
||||
import_batch_no character varying(50),
|
||||
import_file_name character varying(200),
|
||||
import_time timestamp with time zone,
|
||||
@@ -323,7 +329,7 @@ CREATE TABLE dws.dws_assistant_salary_calc (
|
||||
base_course_price numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
bonus_course_price numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
base_deduction numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
bonus_deduction_ratio numeric(5,4) DEFAULT 0 NOT NULL,
|
||||
bonus_deduction_ratio numeric(7,4) DEFAULT 0 NOT NULL,
|
||||
base_income numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
bonus_income numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
room_income numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
@@ -366,7 +372,7 @@ CREATE TABLE dws.dws_finance_daily_summary (
|
||||
platform_fee_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
recharge_cash_inflow numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
card_consume_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
recharge_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_outflow_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_balance_change numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
@@ -394,7 +400,7 @@ CREATE TABLE dws.dws_finance_discount_detail (
|
||||
discount_type_code character varying(30) NOT NULL,
|
||||
discount_type_name character varying(50) NOT NULL,
|
||||
discount_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_ratio numeric(5,4) DEFAULT 0 NOT NULL,
|
||||
discount_ratio numeric(7,4) DEFAULT 0 NOT NULL,
|
||||
usage_count integer DEFAULT 0 NOT NULL,
|
||||
affected_orders integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
@@ -429,7 +435,7 @@ CREATE TABLE dws.dws_finance_income_structure (
|
||||
category_code character varying(30) NOT NULL,
|
||||
category_name character varying(50) NOT NULL,
|
||||
income_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
income_ratio numeric(5,4) DEFAULT 0 NOT NULL,
|
||||
income_ratio numeric(7,4) DEFAULT 0 NOT NULL,
|
||||
order_count integer DEFAULT 0 NOT NULL,
|
||||
duration_minutes integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
@@ -564,7 +570,7 @@ CREATE TABLE dws.dws_member_assistant_intimacy (
|
||||
score_recency numeric(10,4),
|
||||
score_recharge numeric(10,4),
|
||||
score_duration numeric(10,4),
|
||||
burst_multiplier numeric(6,4),
|
||||
burst_multiplier numeric(7,4),
|
||||
raw_score numeric(14,6),
|
||||
display_score numeric(4,2),
|
||||
calc_time timestamp with time zone DEFAULT now() NOT NULL,
|
||||
@@ -720,7 +726,7 @@ CREATE TABLE dws.dws_member_recall_index (
|
||||
|
||||
CREATE TABLE dws.dws_member_spending_power_index (
|
||||
spi_id bigint DEFAULT nextval('dws.dws_member_spending_power_index_spi_id_seq'::regclass) NOT NULL,
|
||||
site_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
spend_30 numeric(14,2) DEFAULT 0,
|
||||
spend_90 numeric(14,2) DEFAULT 0,
|
||||
@@ -767,7 +773,8 @@ CREATE TABLE dws.dws_member_visit_detail (
|
||||
total_discount numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
actual_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
cash_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
cash_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
balance_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
recharge_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
gift_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
groupbuy_pay numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
table_duration_min integer DEFAULT 0 NOT NULL,
|
||||
@@ -1310,7 +1317,7 @@ SELECT id,
|
||||
platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total,
|
||||
cash_card_consume,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
@@ -1357,7 +1364,7 @@ SELECT id,
|
||||
platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total,
|
||||
cash_card_consume,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
@@ -1404,7 +1411,7 @@ SELECT id,
|
||||
platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total,
|
||||
cash_card_consume,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
@@ -1451,7 +1458,7 @@ SELECT id,
|
||||
platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total,
|
||||
cash_card_consume,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
@@ -1483,3 +1490,69 @@ CREATE INDEX idx_mv_finance_daily_l2 ON dws.mv_dws_finance_daily_summary_l2 USIN
|
||||
CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 项目标签表(2026-03-07 新增)
|
||||
-- =============================================================================
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_project_tag_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_member_project_tag_id_seq AS bigint;
|
||||
|
||||
CREATE TABLE dws.dws_assistant_project_tag (
|
||||
id BIGSERIAL NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT NOT NULL,
|
||||
time_window VARCHAR(40) NOT NULL,
|
||||
category_code VARCHAR(30) NOT NULL,
|
||||
category_name VARCHAR(50) NOT NULL,
|
||||
short_name VARCHAR(10) NOT NULL,
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0,
|
||||
total_seconds BIGINT NOT NULL DEFAULT 0,
|
||||
percentage NUMERIC(5,4) NOT NULL DEFAULT 0,
|
||||
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT pk_dws_assistant_project_tag PRIMARY KEY (id),
|
||||
CONSTRAINT uk_dws_assistant_project_tag
|
||||
UNIQUE (site_id, assistant_id, time_window, category_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_assistant_project_tag IS '助教项目标签:按时间窗口计算各项目时长占比,≥25%分配标签';
|
||||
COMMENT ON COLUMN dws.dws_assistant_project_tag.time_window IS '时间窗口:THIS_MONTH/THIS_QUARTER/LAST_MONTH/LAST_3_MONTHS_EXCL_CURRENT/LAST_QUARTER/LAST_6_MONTHS';
|
||||
COMMENT ON COLUMN dws.dws_assistant_project_tag.is_tagged IS '占比≥0.25时为TRUE,表示该助教拥有此项目标签';
|
||||
|
||||
CREATE TABLE dws.dws_member_project_tag (
|
||||
id BIGSERIAL NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
member_id BIGINT NOT NULL,
|
||||
time_window VARCHAR(40) NOT NULL,
|
||||
category_code VARCHAR(30) NOT NULL,
|
||||
category_name VARCHAR(50) NOT NULL,
|
||||
short_name VARCHAR(10) NOT NULL,
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0,
|
||||
total_seconds BIGINT NOT NULL DEFAULT 0,
|
||||
percentage NUMERIC(5,4) NOT NULL DEFAULT 0,
|
||||
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT pk_dws_member_project_tag PRIMARY KEY (id),
|
||||
CONSTRAINT uk_dws_member_project_tag
|
||||
UNIQUE (site_id, member_id, time_window, category_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_member_project_tag IS '客户项目标签:按时间窗口计算各项目消费时长占比,≥25%分配标签';
|
||||
COMMENT ON COLUMN dws.dws_member_project_tag.time_window IS '时间窗口:LAST_30_DAYS/LAST_60_DAYS';
|
||||
COMMENT ON COLUMN dws.dws_member_project_tag.is_tagged IS '占比≥0.25时为TRUE,表示该客户拥有此项目标签';
|
||||
|
||||
-- 部分索引(加速看板查询)
|
||||
CREATE INDEX idx_apt_site_window_tagged
|
||||
ON dws.dws_assistant_project_tag (site_id, time_window)
|
||||
WHERE is_tagged = TRUE;
|
||||
|
||||
CREATE INDEX idx_mpt_site_window_tagged
|
||||
ON dws.dws_member_project_tag (site_id, time_window)
|
||||
WHERE is_tagged = TRUE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / meta(ETL 调度元数据)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / ods(原始数据层)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -77,28 +77,6 @@ CREATE TABLE ods.assistant_accounts_master (
|
||||
payload jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ods.assistant_cancellation_records (
|
||||
id bigint NOT NULL,
|
||||
siteid bigint,
|
||||
siteprofile jsonb,
|
||||
assistantname text,
|
||||
assistantabolishamount numeric(18,2),
|
||||
assistanton integer,
|
||||
pdchargeminutes integer,
|
||||
tableareaid bigint,
|
||||
tablearea text,
|
||||
tableid bigint,
|
||||
tablename text,
|
||||
trashreason text,
|
||||
createtime timestamp without time zone,
|
||||
tenant_id bigint,
|
||||
content_hash text NOT NULL,
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
payload jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ods.assistant_service_records (
|
||||
id bigint NOT NULL,
|
||||
tenant_id bigint,
|
||||
@@ -215,6 +193,7 @@ CREATE TABLE ods.goods_stock_summary (
|
||||
rangesalemoney numeric(18,2),
|
||||
rangeinventory numeric(18,4),
|
||||
currentstock numeric(18,4),
|
||||
siteid bigint,
|
||||
content_hash text NOT NULL,
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
@@ -1066,7 +1045,6 @@ CREATE TABLE ods.tenant_goods_master (
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
ALTER TABLE ods.assistant_accounts_master ADD CONSTRAINT assistant_accounts_master_pkey PRIMARY KEY (id, content_hash);
|
||||
ALTER TABLE ods.assistant_cancellation_records ADD CONSTRAINT assistant_cancellation_records_pkey PRIMARY KEY (id, content_hash);
|
||||
ALTER TABLE ods.assistant_service_records ADD CONSTRAINT assistant_service_records_pkey PRIMARY KEY (id, content_hash);
|
||||
ALTER TABLE ods.goods_stock_movements ADD CONSTRAINT goods_stock_movements_pkey PRIMARY KEY (sitegoodsstockid, content_hash);
|
||||
ALTER TABLE ods.goods_stock_summary ADD CONSTRAINT goods_stock_summary_pkey PRIMARY KEY (sitegoodsid, content_hash);
|
||||
@@ -1091,8 +1069,6 @@ ALTER TABLE ods.tenant_goods_master ADD CONSTRAINT tenant_goods_master_pkey PRIM
|
||||
-- 索引
|
||||
CREATE INDEX idx_assistant_accounts_master_fetched_at_fetched_at ON ods.assistant_accounts_master USING btree (fetched_at);
|
||||
CREATE INDEX idx_assistant_accounts_master_fetched_pk_d986993f ON ods.assistant_accounts_master USING btree (fetched_at, id, content_hash);
|
||||
CREATE INDEX idx_assistant_cancellation_records_fetched_at_fetched_at ON ods.assistant_cancellation_records USING btree (fetched_at);
|
||||
CREATE INDEX idx_assistant_cancellation_records_fetched_pk_258b411c ON ods.assistant_cancellation_records USING btree (fetched_at, id, content_hash);
|
||||
CREATE INDEX idx_assistant_service_records_fetched_at_fetched_at ON ods.assistant_service_records USING btree (fetched_at);
|
||||
CREATE INDEX idx_assistant_service_records_fetched_pk_e200787c ON ods.assistant_service_records USING btree (fetched_at, id, content_hash);
|
||||
CREATE INDEX idx_goods_stock_movements_fetched_at_fetched_at ON ods.goods_stock_movements USING btree (fetched_at);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- FDW 跨库映射(在 zqyy_app 中执行)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:db/fdw/setup_fdw.sql
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / auth(用户认证与权限)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -42,7 +42,7 @@ CREATE TABLE auth.site_code_mapping (
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
tenant_id integer,
|
||||
tenant_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ CREATE TABLE auth.users (
|
||||
wx_avatar_url text,
|
||||
nickname character varying(100),
|
||||
phone character varying(20),
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
status character varying(20) DEFAULT 'new'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
89
docs/database/ddl/zqyy_app__biz.sql
Normal file
89
docs/database/ddl/zqyy_app__biz.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / biz(核心业务表(任务/备注/触发器))
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS biz;
|
||||
|
||||
-- 序列
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_history_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.coach_tasks_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.notes_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.trigger_jobs_id_seq AS integer;
|
||||
|
||||
-- 表
|
||||
CREATE TABLE biz.coach_task_history (
|
||||
id bigint DEFAULT nextval('biz.coach_task_history_id_seq'::regclass) NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
action character varying(50) NOT NULL,
|
||||
old_status character varying(20),
|
||||
new_status character varying(20),
|
||||
old_task_type character varying(50),
|
||||
new_task_type character varying(50),
|
||||
detail jsonb,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.coach_tasks (
|
||||
id bigint DEFAULT nextval('biz.coach_tasks_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
task_type character varying(50) NOT NULL,
|
||||
status character varying(20) DEFAULT 'active'::character varying NOT NULL,
|
||||
priority_score numeric(5,2),
|
||||
expires_at timestamp with time zone,
|
||||
is_pinned boolean DEFAULT false,
|
||||
abandon_reason text,
|
||||
completed_at timestamp with time zone,
|
||||
completed_task_type character varying(50),
|
||||
parent_task_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.notes (
|
||||
id bigint DEFAULT nextval('biz.notes_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
target_type character varying(50) NOT NULL,
|
||||
target_id bigint NOT NULL,
|
||||
type character varying(20) DEFAULT 'normal'::character varying NOT NULL,
|
||||
content text NOT NULL,
|
||||
rating_service_willingness smallint,
|
||||
rating_revisit_likelihood smallint,
|
||||
task_id bigint,
|
||||
ai_score smallint,
|
||||
ai_analysis text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.trigger_jobs (
|
||||
id integer DEFAULT nextval('biz.trigger_jobs_id_seq'::regclass) NOT NULL,
|
||||
job_type character varying(100) NOT NULL,
|
||||
job_name character varying(100) NOT NULL,
|
||||
trigger_condition character varying(20) NOT NULL,
|
||||
trigger_config jsonb NOT NULL,
|
||||
last_run_at timestamp with time zone,
|
||||
next_run_at timestamp with time zone,
|
||||
status character varying(20) DEFAULT 'enabled'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_parent_task_id_fkey FOREIGN KEY (parent_task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.notes ADD CONSTRAINT notes_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.notes ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_job_name_key UNIQUE (job_name);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_coach_tasks_assistant_status ON biz.coach_tasks USING btree (site_id, assistant_id, status);
|
||||
CREATE UNIQUE INDEX idx_coach_tasks_site_assistant_member_type ON biz.coach_tasks USING btree (site_id, assistant_id, member_id, task_type) WHERE ((status)::text = 'active'::text);
|
||||
CREATE INDEX idx_notes_target ON biz.notes USING btree (site_id, target_type, target_id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / public(小程序业务表)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 生成日期:2026-02-27
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -9,7 +9,7 @@ CREATE SCHEMA IF NOT EXISTS public;
|
||||
-- 序列
|
||||
CREATE SEQUENCE IF NOT EXISTS public.admin_users_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.approvals_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.member_birthday_manual_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.member_retention_clue_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.permissions_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS public.tasks_id_seq AS bigint;
|
||||
@@ -37,15 +37,17 @@ CREATE TABLE public.approvals (
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE public.member_birthday_manual (
|
||||
id bigint DEFAULT nextval('member_birthday_manual_id_seq'::regclass) NOT NULL,
|
||||
CREATE TABLE public.member_retention_clue (
|
||||
id bigint DEFAULT nextval('member_retention_clue_id_seq'::regclass) NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
birthday_value date NOT NULL,
|
||||
category character varying(20) NOT NULL,
|
||||
summary character varying(200) NOT NULL,
|
||||
detail text,
|
||||
recorded_by_assistant_id bigint,
|
||||
recorded_by_name character varying(50),
|
||||
recorded_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
source character varying(20) DEFAULT 'assistant'::character varying,
|
||||
site_id bigint NOT NULL
|
||||
site_id bigint NOT NULL,
|
||||
source character varying(20) DEFAULT 'manual'::character varying NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE public.permissions (
|
||||
@@ -149,8 +151,10 @@ ALTER TABLE admin_users ADD CONSTRAINT admin_users_username_key UNIQUE (username
|
||||
ALTER TABLE approvals ADD CONSTRAINT approvals_approver_id_fkey FOREIGN KEY (approver_id) REFERENCES users(id);
|
||||
ALTER TABLE approvals ADD CONSTRAINT approvals_task_id_fkey FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE;
|
||||
ALTER TABLE approvals ADD CONSTRAINT approvals_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE member_birthday_manual ADD CONSTRAINT member_birthday_manual_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE member_birthday_manual ADD CONSTRAINT uk_member_birthday_manual UNIQUE (member_id, recorded_by_assistant_id);
|
||||
ALTER TABLE member_retention_clue ADD CONSTRAINT member_retention_clue_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE member_retention_clue ADD CONSTRAINT chk_retention_clue_category CHECK (
|
||||
category IN ('客户基础', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
);
|
||||
ALTER TABLE permissions ADD CONSTRAINT permissions_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE permissions ADD CONSTRAINT permissions_resource_action_key UNIQUE (resource, action);
|
||||
ALTER TABLE role_permissions ADD CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE;
|
||||
@@ -175,8 +179,9 @@ ALTER TABLE users ADD CONSTRAINT users_wx_openid_key UNIQUE (wx_openid);
|
||||
CREATE INDEX idx_admin_users_site ON public.admin_users USING btree (site_id);
|
||||
CREATE INDEX idx_approvals_site_id ON public.approvals USING btree (site_id);
|
||||
CREATE INDEX idx_approvals_task_id ON public.approvals USING btree (task_id);
|
||||
CREATE INDEX idx_mbd_member ON public.member_birthday_manual USING btree (member_id);
|
||||
CREATE INDEX idx_mbd_site_id ON public.member_birthday_manual USING btree (site_id);
|
||||
CREATE INDEX idx_retention_clue_category ON public.member_retention_clue USING btree (member_id, category);
|
||||
CREATE INDEX idx_retention_clue_member ON public.member_retention_clue USING btree (member_id);
|
||||
CREATE INDEX idx_retention_clue_site ON public.member_retention_clue USING btree (site_id);
|
||||
CREATE INDEX idx_roles_site_id ON public.roles USING btree (site_id);
|
||||
CREATE INDEX idx_scheduled_tasks_next_run ON public.scheduled_tasks USING btree (next_run_at) WHERE (enabled = true);
|
||||
CREATE INDEX idx_scheduled_tasks_site ON public.scheduled_tasks USING btree (site_id);
|
||||
|
||||
Reference in New Issue
Block a user