# BD 手册:助教手动补录会员生日表(C2) ## 概述 为支持助教手动提交客户生日信息(上游 API 未提供时的补充渠道),在 `zqyy_app` / `test_zqyy_app` 业务库中新建 `member_birthday_manual` 表。该表通过 FDW 只读映射供 ETL DWS 任务读取,与 `dim_member.birthday`(API 来源)配合实现生日数据的双源合并。 ## 变更说明 | 库 | Schema | 表 | 变更类型 | 说明 | |----|--------|---|---------|------| | zqyy_app / test_zqyy_app | public | member_birthday_manual | 新建表 | 助教手动补录的会员生日信息 | ### 表结构 | 列名 | 类型 | 约束 | 说明 | |------|------|------|------| | id | BIGSERIAL | PRIMARY KEY | 自增主键 | | member_id | BIGINT | NOT NULL | 会员 ID | | birthday_value | DATE | NOT NULL | 补录的生日值 | | recorded_by_assistant_id | BIGINT | — | 提交助教 ID | | recorded_by_name | VARCHAR(50) | — | 提交助教姓名 | | recorded_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 提交时间 | | source | VARCHAR(20) | DEFAULT 'assistant' | 数据来源标识 | | site_id | BIGINT | NOT NULL | 门店 ID(多门店隔离,Requirements 13.1) | ### 约束与索引 | 名称 | 类型 | 列 | 说明 | |------|------|---|------| | member_birthday_manual_pkey | PRIMARY KEY | id | 主键 | | uk_member_birthday_manual | UNIQUE | (member_id, recorded_by_assistant_id) | 同一助教对同一会员只保留一条记录,支持 UPSERT | | idx_mbd_member | INDEX (btree) | member_id | 加速按会员 ID 查询 | | idx_mbd_site_id | INDEX (btree) | site_id | 加速按门店 ID 查询 | ## 兼容性 - **ETL Connector**:后续通过 FDW 反向映射(`fdw_app.member_birthday_manual`)在 ETL 库中只读访问,DWS 任务使用 `COALESCE(手动补录值, API 值)` 合并生日数据 - **后端 API**:新增 `POST /member-birthday` 接口执行 UPSERT 写入(任务 9.4) - **小程序**:助教端调用后端 API 提交生日,无直接数据库访问 - **现有数据**:新建表,无历史数据影响 ## 回滚策略 ```sql BEGIN; DROP TABLE IF EXISTS member_birthday_manual CASCADE; COMMIT; ``` 回滚无数据丢失风险:该表为新建表,回滚前的数据均为手动补录的生日信息,可由助教重新提交。若已建立 FDW 映射,需先在 ETL 库中删除外部表 `fdw_app.member_birthday_manual`。 ## 验证步骤 ```sql -- 1. 确认表存在 SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'member_birthday_manual'; -- 预期:1 行 -- 2. 确认唯一约束 uk_member_birthday_manual 存在 SELECT conname, contype FROM pg_constraint WHERE conrelid = 'member_birthday_manual'::regclass AND conname = 'uk_member_birthday_manual'; -- 预期:1 行,contype = 'u' -- 3. 确认索引 idx_mbd_member 存在 SELECT indexname FROM pg_indexes WHERE tablename = 'member_birthday_manual' AND indexname = 'idx_mbd_member'; -- 预期:1 行 -- 4. 确认表注释已设置 SELECT obj_description('member_birthday_manual'::regclass, 'pg_class'); -- 预期:'助教手动补录的会员生日信息' -- 5. 确认列结构完整 SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'member_birthday_manual' ORDER BY ordinal_position; -- 预期:8 列(id, member_id, birthday_value, recorded_by_assistant_id, -- recorded_by_name, recorded_at, source, site_id) -- 6. 确认 site_id 索引存在 SELECT indexname FROM pg_indexes WHERE tablename = 'member_birthday_manual' AND indexname = 'idx_mbd_site_id'; -- 预期:1 行 ``` ## 关联文件 - 迁移脚本:`db/zqyy_app/migrations/2026-02-22__C2_member_birthday_manual.sql` - FDW 反向映射(生产):`db/fdw/setup_fdw_reverse.sql` — 在 etl_feiqiu 中创建 `fdw_app.member_birthday_manual` 外部表 - FDW 反向映射(测试):`db/fdw/setup_fdw_reverse_test.sql` — 在 test_etl_feiqiu 中创建外部表 - FDW 反向映射文档:`docs/database/BD_Manual_fdw_reverse_member_birthday.md` - 需求文档:`.kiro/specs/etl-aggregation-fix/requirements.md` — 需求 5.1, 5.3 - 后续任务:9.3(DWS 任务生日读取优先级)、9.4(后端 API 接口)