Files
Neo-ZQYY/docs/database/_archived/etl_feiqiu_schema_migration.md

60 KiB
Raw Blame History

etl_feiqiu Schema 迁移文档


迁移 11修复 DWD 层 BC 哨兵日期 + ETL 代码层哨兵值过滤2026-02-22

迁移文件

db/etl_feiqiu/migrations/2026-02-22__fix_bc_sentinel_dates_to_null.sql

变更说明

修复上游飞球 API 返回的哨兵日期 0001-01-01T00:00:00(表示"未设置")在 ODS→DWD 转换过程中产生的 BC 日期问题。

根因ODS 表中 timestamp without time zone 类型的 0001-01-01 00:00:00 在转入 DWD 表的 timestamp with time zone 列时PostgreSQL 在 Asia/Shanghai 时区下将其隐式转换为 0001-12-31 23:59:43 BC公元前。psycopg2 的 fetchall() 无法将 BC 日期转为 Python datetime,抛出 ValueError: year -1 is out of range

修复内容

类型 说明
存量数据修复 6 个 DWD 表/列中 BC 日期 → NULL约 69,668 行)
代码防御 _cast_expr / _merge_dim_scd2 / _build_fact_select_exprs 三处加哨兵值过滤

受影响表/列

受影响行数
dwd.dim_assistant_ex birth_date ~1,107
dwd.dim_member_card_account_ex disable_start_time ~18,172
dwd.dim_member_card_account_ex disable_end_time ~18,172
dwd.dwd_assistant_service_log_ex composite_grade_time ~5,297
dwd.dwd_recharge_order_ex revoke_time ~485
dwd.dwd_settlement_head_ex revoke_time ~26,435

DDL 结构变更:无(不涉及表/列/索引/约束的新增、删除或修改)

数据变更UPDATE ... SET col = NULL WHERE EXTRACT(year FROM col) < 1,将 BC 日期置为 NULL

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py

  1. _cast_expr:对 timestamptz 类型转换加 CASE WHEN (base)::timestamp >= '0002-01-01'::timestamp THEN ... ELSE NULL END
  2. _build_fact_select_exprs:对 timestamp → timestamptz 同类型列加哨兵过滤
  3. _merge_dim_scd2ODS→DWD SELECT 和 DWD 现有数据读取均加哨兵过滤
  4. 新增类属性 _SENTINEL_DATE_THRESHOLD = "0002-01-01"

关联需求

前后端联调 BUG 12admin-web 任务配置提交执行验证)

兼容性

  • ETL Connector:代码层面已防御,后续 DWD 装载不再产生 BC 日期v10 验证 19/19 任务全部成功
  • 后端 API / 小程序:无影响(不直接查询受影响列)
  • DWS 层无影响DWS 任务不引用这些时间列)
  • 幂等性迁移脚本可重复执行WHERE 条件仅匹配 EXTRACT(year) < 1 的行

回滚策略

存量数据回滚不可行BC 日期本身是错误数据,无业务价值)。代码回滚:

  1. 还原 dwd_load_task.py_cast_expr_build_fact_select_exprs_merge_dim_scd2 的哨兵过滤逻辑
  2. 移除 _SENTINEL_DATE_THRESHOLD 类属性

注意:回滚代码后重新执行 DWD 装载会重新产生 BC 日期,导致 psycopg2 报错。

验证 SQL

-- 1. 确认所有受影响列中不再存在 BC 日期
SELECT 'dim_assistant_ex.birth_date' AS target, COUNT(*) AS bc_count
FROM dwd.dim_assistant_ex WHERE birth_date::text LIKE '%BC%'
UNION ALL
SELECT 'dim_member_card_account_ex.disable_start_time', COUNT(*)
FROM dwd.dim_member_card_account_ex WHERE disable_start_time::text LIKE '%BC%'
UNION ALL
SELECT 'dim_member_card_account_ex.disable_end_time', COUNT(*)
FROM dwd.dim_member_card_account_ex WHERE disable_end_time::text LIKE '%BC%'
UNION ALL
SELECT 'dwd_assistant_service_log_ex.composite_grade_time', COUNT(*)
FROM dwd.dwd_assistant_service_log_ex WHERE composite_grade_time::text LIKE '%BC%'
UNION ALL
SELECT 'dwd_recharge_order_ex.revoke_time', COUNT(*)
FROM dwd.dwd_recharge_order_ex WHERE revoke_time::text LIKE '%BC%'
UNION ALL
SELECT 'dwd_settlement_head_ex.revoke_time', COUNT(*)
FROM dwd.dwd_settlement_head_ex WHERE revoke_time::text LIKE '%BC%';
-- 预期:所有 bc_count = 0

-- 2. 确认受影响列中 NULL 值数量合理(哨兵值已转为 NULL
SELECT 'dim_assistant_ex.birth_date' AS target,
       COUNT(*) FILTER (WHERE birth_date IS NULL) AS null_count,
       COUNT(*) AS total
FROM dwd.dim_assistant_ex WHERE scd2_is_current = 1
UNION ALL
SELECT 'dwd_settlement_head_ex.revoke_time',
       COUNT(*) FILTER (WHERE revoke_time IS NULL),
       COUNT(*)
FROM dwd.dwd_settlement_head_ex;
-- 预期null_count > 0哨兵值行已转为 NULL

-- 3. 确认 DWD 层无 EXTRACT(year) < 1 的异常日期
SELECT 'dim_assistant_ex' AS tbl, COUNT(*) AS bad
FROM dwd.dim_assistant_ex WHERE EXTRACT(year FROM birth_date) < 1
UNION ALL
SELECT 'dim_member_card_account_ex(start)', COUNT(*)
FROM dwd.dim_member_card_account_ex WHERE EXTRACT(year FROM disable_start_time) < 1
UNION ALL
SELECT 'dim_member_card_account_ex(end)', COUNT(*)
FROM dwd.dim_member_card_account_ex WHERE EXTRACT(year FROM disable_end_time) < 1
UNION ALL
SELECT 'dwd_assistant_service_log_ex', COUNT(*)
FROM dwd.dwd_assistant_service_log_ex WHERE EXTRACT(year FROM composite_grade_time) < 1
UNION ALL
SELECT 'dwd_recharge_order_ex', COUNT(*)
FROM dwd.dwd_recharge_order_ex WHERE EXTRACT(year FROM revoke_time) < 1
UNION ALL
SELECT 'dwd_settlement_head_ex', COUNT(*)
FROM dwd.dwd_settlement_head_ex WHERE EXTRACT(year FROM revoke_time) < 1;
-- 预期:所有 bad = 0

-- 4. 确认 ETL v10 执行后无新增 BC 日期(重新装载后仍为 NULL
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name IN ('dim_assistant_ex', 'dim_member_card_account_ex',
                     'dwd_assistant_service_log_ex', 'dwd_recharge_order_ex',
                     'dwd_settlement_head_ex')
  AND data_type = 'timestamp with time zone'
ORDER BY table_name, column_name;
-- 预期:列出所有 timestamptz 列,确认类型未变

迁移 10移除 ods.settlement_ticket_details 表2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__remove_settlement_ticket_details.sql

变更说明

彻底移除 ods.settlement_ticket_details 表及其关联资源。该表对应的上游 API 数据已由 ods.settlement_records 完全覆盖,不再需要独立的结算小票明细表。

移除项 说明
ods.settlement_ticket_details ODS 结算小票明细表(含 CREATE TABLE + 全部 COMMENT
ods.idx_ods_settlement_ticket_details_latest 索引 取最新版本复合索引
meta.ods_task_registryODS_SETTLEMENT_TICKET 条目 任务注册记录
db/etl_feiqiu/seeds/seed_ods_tasks.sqlODS_SETTLEMENT_TICKET 种子数据条目
db/etl_feiqiu/schemas/ods.sql 中建表语句和注释 DDL 源文件
db/etl_feiqiu/schemas/schema_ODS_doc.sql 中建表语句和注释 旧版 DDL 文档文件

关联需求

dataflow-field-completion Requirements 11.111.8

兼容性

  • ETL ConnectorODS_SETTLEMENT_TICKET 任务已从任务注册和种子数据中移除,不再调度
  • 后端 API / 小程序:无下游依赖此表,无影响
  • DWD / DWS 层:无 DWD/DWS 表映射自此 ODS 表,无影响

回滚策略

  1. 从 git 历史恢复 ods.sqlschema_ODS_doc.sql 中的建表语句
  2. 重新创建表和索引:
-- 从 git 历史中提取原始 CREATE TABLE 语句执行
-- 重建索引
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_settlement_ticket_details_latest
    ON ods.settlement_ticket_details (orderSettleId, fetched_at DESC);
-- 重新注册任务
INSERT INTO meta.ods_task_registry (task_code, store_id, enabled)
VALUES ('ODS_SETTLEMENT_TICKET', 2790685415443269, TRUE)
ON CONFLICT DO NOTHING;

验证 SQL

-- 1. 确认表已不存在
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'ods' AND table_name = 'settlement_ticket_details';
-- 预期0 行

-- 2. 确认索引已不存在
SELECT indexname FROM pg_indexes
WHERE schemaname = 'ods' AND indexname = 'idx_ods_settlement_ticket_details_latest';
-- 预期0 行

-- 3. 确认任务注册已移除
SELECT task_code FROM meta.ods_task_registry
WHERE task_code = 'ODS_SETTLEMENT_TICKET';
-- 预期0 行

迁移 9为 dim_table_ex 新增 14 个字段2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__add_dim_table_ex_fields.sql

变更说明

dwd.dim_table_ex 新增 14 个字段,映射自 ODS site_tables_master 中已有的同名列(部分驼峰式在 PostgreSQL 中已小写化)。

新增列 类型 业务含义
create_time TIMESTAMPTZ 台桌配置的创建时间或最近一次创建/复制时间
light_status INTEGER 台灯状态枚举(如 2=已开灯)
tablestatusname TEXT 台桌状态中文名称(如"空闲中""使用中"),仅展示用途
sitename TEXT 门店名称快照,冗余字段,配合 site_id 使用
applet_qr_code_url TEXT 小程序二维码 URL用于扫码开台等场景
audit_status INTEGER 审核状态枚举(当前全部为 2
charge_free INTEGER 是否免费台0=收费1=免费)
delay_lights_time INTEGER 台灯熄灭延迟时间,结账后延时关灯
is_rest_area INTEGER 是否休息区台桌0=否1=是)
only_allow_groupon INTEGER 是否仅允许团购开台0/1/2 枚举)
order_delay_time INTEGER 订单自动延时时长
self_table INTEGER 是否自有台桌1=自有)
temporary_light_second INTEGER 临时开灯秒数
virtual_table INTEGER 是否虚拟台桌0=实体台1=虚拟台)

DDL 结构变更ALTER TABLE ... ADD COLUMN × 14

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dim_table_ex"] 新增 14 个映射条目。

ODS→DWD 列名映射说明

  • 大部分字段为同名直通映射ODS 列名在 PG 中已小写化)
  • applet_qr_code_url ← ODS "appletQrCodeUrl"ODS DDL 中用双引号保留驼峰大小写FACT_MAPPINGS 中需带引号引用)
  • tablestatusname ← ODS tablestatusnameDDL 中写 tableStatusNamePG 自动小写化)
  • sitename ← ODS sitenameDDL 中写 siteNamePG 自动小写化)

关联需求

dataflow-field-completion Requirements 9.1, 9.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新列数据ODS 源列已存在
  • 后端 API / 小程序:新增列不影响现有查询;如需使用新字段需在下游查询中显式引用
  • DWS 层:当前无 DWS 任务引用这 14 列,无影响

回滚策略

BEGIN;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS create_time;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS light_status;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS tablestatusname;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS sitename;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS applet_qr_code_url;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS audit_status;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS charge_free;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS delay_lights_time;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS is_rest_area;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS only_allow_groupon;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS order_delay_time;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS self_table;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS temporary_light_second;
ALTER TABLE dwd.dim_table_ex DROP COLUMN IF EXISTS virtual_table;
COMMIT;

验证 SQL

-- 1. 确认 14 个新列均已创建且类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dim_table_ex'
  AND column_name IN (
    'create_time', 'light_status', 'tablestatusname', 'sitename',
    'applet_qr_code_url', 'audit_status', 'charge_free', 'delay_lights_time',
    'is_rest_area', 'only_allow_groupon', 'order_delay_time', 'self_table',
    'temporary_light_second', 'virtual_table'
  )
ORDER BY column_name;

-- 2. 确认列注释已添加(应返回 14 行)
SELECT column_name,
       col_description(
           (SELECT oid FROM pg_class WHERE relname = 'dim_table_ex'
            AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
           attnum
       ) AS comment
FROM pg_attribute
WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dim_table_ex'
      AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
  AND attname IN (
    'create_time', 'light_status', 'tablestatusname', 'sitename',
    'applet_qr_code_url', 'audit_status', 'charge_free', 'delay_lights_time',
    'is_rest_area', 'only_allow_groupon', 'order_delay_time', 'self_table',
    'temporary_light_second', 'virtual_table'
  );

-- 3. 确认 ODS 源表中对应列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name = 'site_tables_master'
  AND column_name IN (
    'create_time', 'light_status', 'tablestatusname', 'sitename',
    'appletQrCodeUrl', 'audit_status', 'charge_free', 'delay_lights_time',
    'is_rest_area', 'only_allow_groupon', 'order_delay_time', 'self_table',
    'temporary_light_second', 'virtual_table'
  )
ORDER BY column_name;

-- 4. 重新加载后抽样验证新列有数据
SELECT create_time, light_status, tablestatusname, sitename,
       applet_qr_code_url, audit_status, charge_free
FROM dwd.dim_table_ex
WHERE scd2_is_current = 1
LIMIT 10;

迁移 8为 dwd_member_balance_change_ex 新增 relate_id 字段2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__add_member_balance_change_ex_relate_id.sql

变更说明

dwd.dwd_member_balance_change_ex 新增 1 个字段,映射自 ODS member_balance_changes 中已有的同名列。

新增列 类型 业务含义
relate_id BIGINT 关联业务单据 ID指向触发本次余额变动的业务记录充值单、订单、结算单等from_type 不同指向不同表

DDL 结构变更ALTER TABLE ... ADD COLUMN × 1

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_member_balance_change_ex"] 新增 1 个映射条目:

  • ("relate_id", "relate_id", None)

关联需求

dataflow-field-completion Requirements 5.1, 5.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新列数据ODS 源列已存在,映射为同名直通
  • 后端 API / 小程序:新增列不影响现有查询;如需使用新字段需在下游查询中显式引用
  • DWS 层:当前无 DWS 任务引用此列,无影响

回滚策略

BEGIN;
ALTER TABLE dwd.dwd_member_balance_change_ex DROP COLUMN IF EXISTS relate_id;
COMMIT;

验证 SQL

-- 1. 确认新列已创建且类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_member_balance_change_ex'
  AND column_name = 'relate_id';

-- 2. 确认列注释已添加
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dwd_member_balance_change_ex'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dwd_member_balance_change_ex'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'relate_id')
) AS relate_id_comment;

-- 3. 确认 ODS 源表中同名列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name = 'member_balance_changes'
  AND column_name = 'relate_id';

-- 4. 重新加载后抽样验证新列有数据
SELECT relate_id, balance_change_id
FROM dwd.dwd_member_balance_change_ex
WHERE relate_id IS NOT NULL
LIMIT 10;

迁移 7为 dwd_assistant_service_log_ex 新增 2 个字段2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__add_assistant_service_log_ex_fields.sql

变更说明

dwd.dwd_assistant_service_log_ex 新增 2 个字段,映射自 ODS assistant_service_records 中已有的同名列。

新增列 类型 业务含义
operator_id BIGINT 操作员 ID录入/结算这条助教服务的员工
operator_name TEXT 操作员姓名(带职位前缀),便于直接阅读

DDL 结构变更ALTER TABLE ... ADD COLUMN × 2

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_assistant_service_log_ex"] 新增 2 个映射条目:

  • ("operator_id", "operator_id", None)
  • ("operator_name", "operator_name", None)

关联需求

dataflow-field-completion Requirements 2.1, 2.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新列数据ODS 源列已存在,映射为同名直通
  • 后端 API / 小程序:新增列不影响现有查询;如需使用新字段需在下游查询中显式引用
  • DWS 层:当前无 DWS 任务引用这 2 列,无影响

回滚策略

BEGIN;
ALTER TABLE dwd.dwd_assistant_service_log_ex DROP COLUMN IF EXISTS operator_id;
ALTER TABLE dwd.dwd_assistant_service_log_ex DROP COLUMN IF EXISTS operator_name;
COMMIT;

验证 SQL

-- 1. 确认 2 个新列均已创建且类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_assistant_service_log_ex'
  AND column_name IN ('operator_id', 'operator_name')
ORDER BY column_name;

-- 2. 确认列注释已添加
SELECT column_name,
       col_description(
           (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log_ex'
            AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
           attnum
       ) AS comment
FROM pg_attribute
WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log_ex'
      AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
  AND attname IN ('operator_id', 'operator_name');

-- 3. 确认 ODS 源表中同名列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name = 'assistant_service_records'
  AND column_name IN ('operator_id', 'operator_name')
ORDER BY column_name;

-- 4. 重新加载后抽样验证新列有数据
SELECT operator_id, operator_name
FROM dwd.dwd_assistant_service_log_ex
WHERE operator_id IS NOT NULL
LIMIT 10;

迁移 6为 dim_assistant_ex 新增 4 个字段2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__add_dim_assistant_ex_fields.sql

变更说明

dwd.dim_assistant_ex 新增 4 个字段,映射自 ODS assistant_accounts_master 中已有的同名列。

新增列 类型 业务含义
system_role_id BIGINT 系统角色 ID标识助教在系统中的角色类型
job_num TEXT 工号,助教的内部编号标识
cx_unit_price NUMERIC(18,2) 促销单价(元),助教提供促销服务时的计费单价
pd_unit_price NUMERIC(18,2) 陪打单价(元),助教提供陪打服务时的计费单价

DDL 结构变更ALTER TABLE ... ADD COLUMN × 4

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dim_assistant_ex"] 新增 4 个映射条目:

  • ("system_role_id", "system_role_id", None)
  • ("job_num", "job_num", None)
  • ("cx_unit_price", "cx_unit_price", None)
  • ("pd_unit_price", "pd_unit_price", None)

关联需求

dataflow-field-completion Requirements 1.1, 1.2, 1.3

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新列数据ODS 源列已存在,映射为同名直通
  • 后端 API / 小程序:新增列不影响现有查询;如需使用新字段需在下游查询中显式引用
  • DWS 层:当前无 DWS 任务引用这 4 列,无影响

回滚策略

BEGIN;
ALTER TABLE dwd.dim_assistant_ex DROP COLUMN IF EXISTS system_role_id;
ALTER TABLE dwd.dim_assistant_ex DROP COLUMN IF EXISTS job_num;
ALTER TABLE dwd.dim_assistant_ex DROP COLUMN IF EXISTS cx_unit_price;
ALTER TABLE dwd.dim_assistant_ex DROP COLUMN IF EXISTS pd_unit_price;
COMMIT;

验证 SQL

-- 1. 确认 4 个新列均已创建且类型正确
SELECT column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dim_assistant_ex'
  AND column_name IN ('system_role_id', 'job_num', 'cx_unit_price', 'pd_unit_price')
ORDER BY column_name;

-- 2. 确认列注释已添加
SELECT column_name,
       col_description(
           (SELECT oid FROM pg_class WHERE relname = 'dim_assistant_ex'
            AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
           attnum
       ) AS comment
FROM pg_attribute
WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dim_assistant_ex'
      AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
  AND attname IN ('system_role_id', 'job_num', 'cx_unit_price', 'pd_unit_price');

-- 3. 确认 ODS 源表中同名列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name = 'assistant_accounts_master'
  AND column_name IN ('system_role_id', 'job_num', 'cx_unit_price', 'pd_unit_price')
ORDER BY column_name;

-- 4. 重新加载后抽样验证新列有数据
SELECT system_role_id, job_num, cx_unit_price, pd_unit_price
FROM dwd.dim_assistant_ex
WHERE scd2_is_current = 1
LIMIT 10;

迁移 5修正 dim_store_goods.batch_stock_qty 和 dim_store_goods_ex.provisional_total_cost 映射源2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__fix_store_goods_master_mapping.sql

变更说明

修正 dwd.dim_store_goods.batch_stock_qtydwd.dim_store_goods_ex.provisional_total_cost 的 ODS 映射源错误。

项目 修正前 修正后
batch_stock_qty 的 ODS 源 stock(当前库存) batch_stock_quantity(批次库存)
provisional_total_cost 的 ODS 源 total_purchase_cost(实际采购成本) provisional_total_cost(暂估成本)

DDL 层面无结构变更(两列均已存在),仅更新列注释以反映正确的 ODS 来源。

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py 中:

  • FACT_MAPPINGS["dwd.dim_store_goods"]batch_stock_qty 条目从 ("batch_stock_qty", "stock", None) 改为 ("batch_stock_qty", "batch_stock_quantity", None)
  • FACT_MAPPINGS["dwd.dim_store_goods_ex"]provisional_total_cost 条目从 ("provisional_total_cost", "total_purchase_cost", None) 改为 ("provisional_total_cost", "provisional_total_cost", None)

关联需求

dataflow-field-completion Requirements 10.3

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以修正历史数据中两列的值
  • 后端 API / 小程序:如有下游查询依赖 batch_stock_qtyprovisional_total_cost,修正后数据将更准确
  • DWS 层:当前无 DWS 任务引用这两列,无影响

回滚策略

  1. FACT_MAPPINGSbatch_stock_qty 的 ODS 源改回 stock
  2. FACT_MAPPINGSprovisional_total_cost 的 ODS 源改回 total_purchase_cost
  3. 恢复 dwd.sql 中的原始 COMMENT
  4. 重新执行 DWD 加载任务
BEGIN;
COMMENT ON COLUMN dwd.dim_store_goods.batch_stock_qty IS
    '【说明】数量/时长字段,用于统计与计量。     【示例】18数量/时长字段,用于统计与计量)。     【ODS来源】store_goods_master - stock。     【JSON字段】store_goods_master.json - data.orderGoodsList - stock。';
COMMENT ON COLUMN dwd.dim_store_goods_ex.provisional_total_cost IS
    '【说明】金额字段,用于计费/结算/核算等金额计算。     【示例】0.0(金额字段,用于计费/结算/核算等金额计算)。     【ODS来源】store_goods_master - total_purchase_cost。     【JSON字段】store_goods_master.json - data.orderGoodsList - total_purchase_cost。';
COMMIT;

验证 SQL

-- 1. 确认 batch_stock_qty 注释已更新为 batch_stock_quantity
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'batch_stock_qty')
) AS batch_stock_qty_comment;

-- 2. 确认 provisional_total_cost 注释已更新
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods_ex'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods_ex'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'provisional_total_cost')
) AS provisional_total_cost_comment;

-- 3. 确认两列均存在且类型未变
SELECT column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND (
    (table_name = 'dim_store_goods' AND column_name = 'batch_stock_qty')
    OR
    (table_name = 'dim_store_goods_ex' AND column_name = 'provisional_total_cost')
  )
ORDER BY table_name, column_name;

-- 4. 重新加载后抽样验证 batch_stock_qty 来自 batch_stock_quantity
SELECT g.batch_stock_qty AS dwd_val,
       o.batch_stock_quantity AS ods_batch,
       o.stock AS ods_stock
FROM dwd.dim_store_goods g
JOIN (SELECT DISTINCT ON (id) * FROM ods.store_goods_master ORDER BY id, fetched_at DESC) o
  ON g.site_goods_id = o.id
WHERE o.batch_stock_quantity IS DISTINCT FROM o.stock
LIMIT 20;

迁移 4修正 dwd_store_goods_sale.discount_price 列名误导2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__fix_store_goods_sale_discount_price.sql

变更说明

修正 dwd.dwd_store_goods_sale.discount_price 列名语义误导问题。

项目 修正前 修正后
DWD discount_price 映射自 ODS discount_money(折扣金额),列名与实际语义不符 重命名为 discount_money,反映真实语义
DWD discount_price 列(新增) 不存在 新增,映射自 ODS discount_price(折后单价)

DDL 结构变更

  1. RENAME COLUMN discount_price TO discount_money
  2. ADD COLUMN discount_price NUMERIC(18,2)

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_store_goods_sale"]

  • ("discount_price", "discount_money", None)("discount_money", "discount_money", None)
  • 新增 ("discount_price", "discount_price", None)

关联需求

dataflow-field-completion Requirements 4.1, 4.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新 discount_price 列数据;discount_money 列数据在 RENAME 后自动保留
  • 后端 API / 小程序:如有下游查询引用 discount_price 列,需注意该列语义已变更(原为折扣金额,现为折后单价);原折扣金额数据现在位于 discount_money
  • DWS 层:当前无 DWS 任务引用此列,无影响

回滚策略

  1. 删除新增的 discount_price
  2. discount_money 重命名回 discount_price
  3. 恢复 FACT_MAPPINGSdwd.sql 中的原始定义
BEGIN;
ALTER TABLE dwd.dwd_store_goods_sale DROP COLUMN IF EXISTS discount_price;
ALTER TABLE dwd.dwd_store_goods_sale RENAME COLUMN discount_money TO discount_price;
COMMENT ON COLUMN dwd.dwd_store_goods_sale.discount_price IS
    '【说明】金额字段,用于计费/结算/核算等金额计算。     【示例】0.0(金额字段,用于计费/结算/核算等金额计算)。     【ODS来源】store_goods_sales_records - discount_money。     【JSON字段】store_goods_sales_records.json - data.orderGoodsLedgers - discount_money。';
COMMIT;

验证 SQL

-- 1. 确认 discount_money 列存在且类型正确
SELECT column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_store_goods_sale'
  AND column_name = 'discount_money';

-- 2. 确认 discount_price 列已新增且类型正确
SELECT column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_store_goods_sale'
  AND column_name = 'discount_price';

-- 3. 确认两列均存在(应返回 2 行)
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_store_goods_sale'
  AND column_name IN ('discount_money', 'discount_price')
ORDER BY column_name;

-- 4. 重新加载后抽样验证两列值不同
SELECT discount_money, discount_price,
       (discount_money = discount_price) AS same
FROM dwd.dwd_store_goods_sale
WHERE discount_price IS NOT NULL
LIMIT 20;

迁移 3修正 dwd_assistant_service_log.site_assistant_id 映射源2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__fix_assistant_service_site_assistant_id.sql

变更说明

修正 dwd.dwd_assistant_service_log.site_assistant_id 的映射源错误。

项目 修正前 修正后
site_assistant_id 的 ODS 源 order_assistant_id(订单级助教明细 ID site_assistant_id(门店维度助教档案 ID
order_assistant_id 已存在于 DDL但被 site_assistant_id 的映射占用 恢复为同名自动映射

DDL 层面无结构变更(两列均已存在),仅更新列注释以反映正确的 ODS 来源。

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_assistant_service_log"]site_assistant_id 条目从 ("site_assistant_id", "order_assistant_id", None) 改为 ("site_assistant_id", "site_assistant_id", None)

关联需求

dataflow-field-completion Requirements 2.1, 2.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以修正历史数据中 site_assistant_id 的值
  • 后端 API / 小程序:如有下游查询依赖 site_assistant_id 关联助教档案,修正后数据将更准确
  • DWS 层:依赖 site_assistant_id 的汇总表(如助教业绩)需在 DWD 重新加载后重新计算

回滚策略

  1. FACT_MAPPINGSsite_assistant_id 的 ODS 源改回 order_assistant_id
  2. 恢复 dwd.sql 中的原始 COMMENT
  3. 重新执行 DWD 加载任务
-- 恢复原始 COMMENT
COMMENT ON COLUMN dwd.dwd_assistant_service_log.site_assistant_id IS
    '【说明】标识类 ID 字段,用于关联/定位相关实体。     【示例】2957788717240005。     【ODS来源】assistant_service_records - order_assistant_id。';
COMMENT ON COLUMN dwd.dwd_assistant_service_log.order_assistant_id IS
    '【说明】标识类 ID 字段,用于关联/定位相关实体。     【示例】2957788717240005。     【ODS来源】assistant_service_records - order_assistant_id。';

验证 SQL

-- 1. 确认 site_assistant_id 注释已更新为正确的 ODS 来源
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'site_assistant_id')
);

-- 2. 确认 order_assistant_id 注释已更新
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_service_log'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'order_assistant_id')
);

-- 3. 重新加载后抽样验证两列值不同site_assistant_id 应为档案级 ID
SELECT site_assistant_id, order_assistant_id,
       (site_assistant_id = order_assistant_id) AS same
FROM dwd.dwd_assistant_service_log
LIMIT 20;

迁移 2ODS "取最新版本"复合索引2026-02-17

迁移文件

db/etl_feiqiu/migrations/2026-02-17__add_ods_latest_version_indexes.sql

变更说明

为全部 23 张 ODS 表添加 (业务主键, fetched_at DESC) 复合索引,支持 DISTINCT ON (pk) ORDER BY pk, fetched_at DESC 查询模式高效取每条业务记录的最新版本。

索引命名规范:idx_ods_{table_name}_latest

# 表名 业务主键列 索引名
1 assistant_accounts_master id idx_ods_assistant_accounts_master_latest
2 settlement_records id idx_ods_settlement_records_latest
3 table_fee_transactions id idx_ods_table_fee_transactions_latest
4 assistant_service_records id idx_ods_assistant_service_records_latest
5 assistant_cancellation_records id idx_ods_assistant_cancellation_records_latest
6 store_goods_sales_records id idx_ods_store_goods_sales_records_latest
7 payment_transactions id idx_ods_payment_transactions_latest
8 refund_transactions id idx_ods_refund_transactions_latest
9 platform_coupon_redemption_records id idx_ods_platform_coupon_redemption_records_latest
10 member_profiles id idx_ods_member_profiles_latest
11 member_stored_value_cards id idx_ods_member_stored_value_cards_latest
12 member_balance_changes id idx_ods_member_balance_changes_latest
13 recharge_settlements id idx_ods_recharge_settlements_latest
14 group_buy_packages id idx_ods_group_buy_packages_latest
15 group_buy_redemption_records id idx_ods_group_buy_redemption_records_latest
16 goods_stock_summary siteGoodsId idx_ods_goods_stock_summary_latest
17 goods_stock_movements siteGoodsStockId idx_ods_goods_stock_movements_latest
18 site_tables_master id idx_ods_site_tables_master_latest
19 stock_goods_category_tree id idx_ods_stock_goods_category_tree_latest
20 store_goods_master id idx_ods_store_goods_master_latest
21 table_fee_discount_records id idx_ods_table_fee_discount_records_latest
22 tenant_goods_master id idx_ods_tenant_goods_master_latest
23 settlement_ticket_details orderSettleId idx_ods_settlement_ticket_details_latest(已由迁移 10 移除)

关联需求

ods-dedup-standardize Requirements 6.1, 6.2, 6.3

兼容性

  • 非破坏性变更:仅新增索引,不修改表结构、不影响现有数据和写入逻辑
  • ETL Connector:无需改动;索引自动加速 skip_unchanged 去重查询和 _mark_missing_as_deleted 快照对比查询
  • 后端 API / 小程序:不受影响(不直接查询 ODS 层)
  • CREATE INDEX CONCURRENTLY:在线创建,不阻塞表的读写操作;但不能在事务块内执行,需逐条运行或使用支持单语句模式的工具
  • DDL 源文件:索引定义已同步写入 db/etl_feiqiu/schemas/ods.sql,新环境初始化时自动创建

回滚策略

逐条删除索引即可,不影响数据:

DROP INDEX IF EXISTS ods.idx_ods_assistant_accounts_master_latest;
DROP INDEX IF EXISTS ods.idx_ods_settlement_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_table_fee_transactions_latest;
DROP INDEX IF EXISTS ods.idx_ods_assistant_service_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_assistant_cancellation_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_store_goods_sales_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_payment_transactions_latest;
DROP INDEX IF EXISTS ods.idx_ods_refund_transactions_latest;
DROP INDEX IF EXISTS ods.idx_ods_platform_coupon_redemption_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_member_profiles_latest;
DROP INDEX IF EXISTS ods.idx_ods_member_stored_value_cards_latest;
DROP INDEX IF EXISTS ods.idx_ods_member_balance_changes_latest;
DROP INDEX IF EXISTS ods.idx_ods_recharge_settlements_latest;
DROP INDEX IF EXISTS ods.idx_ods_group_buy_packages_latest;
DROP INDEX IF EXISTS ods.idx_ods_group_buy_redemption_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_goods_stock_summary_latest;
DROP INDEX IF EXISTS ods.idx_ods_goods_stock_movements_latest;
DROP INDEX IF EXISTS ods.idx_ods_site_tables_master_latest;
DROP INDEX IF EXISTS ods.idx_ods_stock_goods_category_tree_latest;
DROP INDEX IF EXISTS ods.idx_ods_store_goods_master_latest;
DROP INDEX IF EXISTS ods.idx_ods_table_fee_discount_records_latest;
DROP INDEX IF EXISTS ods.idx_ods_tenant_goods_master_latest;
-- idx_ods_settlement_ticket_details_latest 已由迁移 10 随表一起移除

验证 SQL

-- 1. 验证 23 个索引均已创建
SELECT indexname, tablename
FROM pg_indexes
WHERE schemaname = 'ods'
  AND indexname LIKE 'idx_ods_%_latest'
ORDER BY indexname;

-- 2. 验证索引数量为 23
SELECT COUNT(*) AS index_count
FROM pg_indexes
WHERE schemaname = 'ods'
  AND indexname LIKE 'idx_ods_%_latest';

-- 3. 验证索引列定义正确(以 member_profiles 为例)
SELECT indexdef
FROM pg_indexes
WHERE schemaname = 'ods'
  AND indexname = 'idx_ods_member_profiles_latest';

-- 4. 验证索引可用(非 INVALID 状态)
SELECT c.relname AS index_name, i.indisvalid
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'ods'
  AND c.relname LIKE 'idx_ods_%_latest'
  AND i.indisvalid = false;
-- 预期结果0 行(无无效索引)

迁移 1六层 Schema 架构重组

变更说明

将现有 4 个 schema 重组为 6 层 schema 架构:

原 Schema 新 Schema 文件 说明
etl_admin meta meta.sql 调度、游标、运行记录3 表)
billiards_ods ods ods.sql ODS 原始数据23 表)
billiards_dwd dwd dwd.sql DWD 明细,保留 main+EX 拆分40 表)
(新增) core core.sql 统一维度/事实最小字段集7 表)
billiards_dws dws dws.sql DWS 汇总29 表)
(新增) app app.sql 视图+RLS7 视图6 策略)

新增表core schema

  • core.dim_site — 门店维度核心字段
  • core.dim_member — 会员维度核心字段
  • core.dim_assistant — 助教维度核心字段
  • core.dim_table — 台桌维度核心字段
  • core.dim_goods_category — 商品分类维度核心字段
  • core.fact_settlement — 结算事实核心字段
  • core.fact_payment — 支付事实核心字段

新增视图app schema

  • pp.v_site — 门店视图
  • pp.v_member — 会员视图
  • pp.v_assistant — 助教视图
  • pp.v_assistant_daily — 助教日明细视图
  • pp.v_finance_daily — 财务日报视图
  • pp.v_member_consumption — 会员消费汇总视图
  • pp.v_order_summary — 订单汇总视图

RLS 策略

  • 所有 core 表启用 ROW LEVEL SECURITY
  • 策略基于 current_setting('app.current_site_id')::bigint 过滤
  • 角色 pp_reader 仅有 SELECT 权限

兼容性

  • ETL Connector:所有代码中的 schema 引用已更新完成etl_admin → meta, billiards_ods → ods, billiards_dwd → dwd, billiards_dws → dws
  • 后端 APIetl_status 路由已更新为 meta.etl_cursor通过 app schema 视图访问,无需直接引用底层表
  • 管理后台:已由 apps/admin-web/ Web 管理后台完全替代原 PySide6 桌面 GUI通过后端 API 访问数据
  • 小程序:通过 FDW 映射 app schema不受影响

回滚策略

  1. 删除新 schemaDROP SCHEMA IF EXISTS meta, ods, dwd, core, dws, app CASCADE;
  2. 重建原 schema执行原始 schema_etl_admin.sql、schema_ODS_doc.sql、schema_dwd_doc.sql、schema_dws.sql
  3. 原始 DDL 文件保留在 db/etl_feiqiu/schemas/schema_*.sql 作为参考

验证 SQL

`sql -- 1. 验证六个 schema 均已创建 SELECT schema_name FROM information_schema.schemata WHERE schema_name IN ('meta', 'ods', 'dwd', 'core', 'dws', 'app') ORDER BY schema_name;

-- 2. 验证各 schema 表数量 SELECT table_schema, COUNT(*) AS table_count FROM information_schema.tables WHERE table_schema IN ('meta', 'ods', 'dwd', 'core', 'dws', 'app') GROUP BY table_schema ORDER BY table_schema;

-- 3. 验证 RLS 策略已启用 SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'core' AND rowsecurity = true;

-- 4. 验证 app_reader 角色存在 SELECT rolname FROM pg_roles WHERE rolname = 'app_reader'; `

迁移 11新建 dwd.dwd_goods_stock_summary 表2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__create_dwd_goods_stock_summary.sql

变更说明

新建 dwd.dwd_goods_stock_summary 表,用于存储库存汇总快照数据。

项目 说明
新建表 dwd.dwd_goods_stock_summary14 个业务字段 + site_id/tenant_id/fetched_at
主键 (site_goods_id, fetched_at),支持同一商品多时间窗口快照
ODS 配置变更 ODS_INVENTORY_STOCKrequires_window=True + time_fields=("startTime", "endTime")
代码变更 TABLE_MAPFACT_MAPPINGS 中注册新表14 个 ODS 驼峰→DWD 蛇形映射

新增字段

DWD 列 类型 ODS 源列 业务含义
site_goods_id BIGINT (PK) "siteGoodsId" 门店商品 ID
goods_name TEXT "goodsName" 商品名称
goods_unit TEXT "goodsUnit" 计量单位
goods_category_id BIGINT "goodsCategoryId" 一级分类 ID
goods_category_second_id BIGINT "goodsCategorySecondId" 二级分类 ID
category_name TEXT "categoryName" 分类名称
range_start_stock NUMERIC "rangeStartStock" 期初库存
range_end_stock NUMERIC "rangeEndStock" 期末库存
range_in NUMERIC "rangeIn" 入库数量
range_out NUMERIC "rangeOut" 出库数量
range_sale NUMERIC "rangeSale" 销售数量
range_sale_money NUMERIC(12,2) "rangeSaleMoney" 销售金额
range_inventory NUMERIC "rangeInventory" 盘点调整量
current_stock NUMERIC "currentStock" 当前库存

关联需求

dataflow-field-completion Requirements 7.1, 7.2, 7.3, 7.4

兼容性

  • ETL Connector新增表不影响现有流程ODS 配置变更后需重新采集历史数据(按时间窗口分批)
  • 后端 API / 小程序:无影响,新表需下游显式引用
  • DWS 层:此表为 DWS 库存汇总(迁移 13的数据源

回滚策略

DROP TABLE IF EXISTS dwd.dwd_goods_stock_summary;
-- 恢复 ODS 配置requires_window=False移除 time_fields
-- 恢复 TABLE_MAP 和 FACT_MAPPINGS移除 dwd.dwd_goods_stock_summary 条目

验证 SQL

-- 1. 确认表已创建
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_summary';

-- 2. 确认列数量正确17 列14 业务 + site_id + tenant_id + fetched_at
SELECT count(*) FROM information_schema.columns
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_summary';

-- 3. 确认主键约束
SELECT constraint_name, column_name FROM information_schema.key_column_usage
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_summary'
ORDER BY ordinal_position;

-- 4. 确认 ODS 源表存在
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'ods' AND table_name = 'goods_stock_summary';

迁移 12新建 dwd.dwd_goods_stock_movement 表2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__create_dwd_goods_stock_movement.sql

变更说明

新建 dwd.dwd_goods_stock_movement 表,用于存储库存变动流水明细。

项目 说明
新建表 dwd.dwd_goods_stock_movement19 个业务字段 + fetched_at
主键 site_goods_stock_id(库存变动记录 ID
加载模式 事实表,按 create_time 增量加载
代码变更 TABLE_MAPFACT_MAPPINGS 中注册新表19 个 ODS 驼峰→DWD 蛇形映射

新增字段

DWD 列 类型 ODS 源列 业务含义
site_goods_stock_id BIGINT (PK) "siteGoodsStockId" 库存变动记录 ID
tenant_id BIGINT "tenantId" 租户 ID
site_id BIGINT "siteId" 门店 ID
site_goods_id BIGINT "siteGoodsId" 门店商品 ID
goods_name TEXT "goodsName" 商品名称
goods_category_id BIGINT "goodsCategoryId" 一级分类 ID
goods_second_category_id BIGINT "goodsSecondCategoryId" 二级分类 ID
unit TEXT unit 计量单位
price NUMERIC(12,2) price 商品单价
stock_type INTEGER "stockType" 库存变动类型枚举
change_num NUMERIC "changeNum" 变动数量
start_num NUMERIC "startNum" 变动前库存
end_num NUMERIC "endNum" 变动后库存
change_num_a NUMERIC "changeNumA" 辅助单位变动量
start_num_a NUMERIC "startNumA" 辅助单位变动前库存
end_num_a NUMERIC "endNumA" 辅助单位变动后库存
remark TEXT remark 备注
operator_name TEXT "operatorName" 操作人
create_time TIMESTAMPTZ "createTime" 变动时间

关联需求

dataflow-field-completion Requirements 8.1, 8.2, 8.3, 8.4

兼容性

  • ETL Connector新增表不影响现有流程ODS 源表 ods.goods_stock_movements 已存在且有数据
  • 后端 API / 小程序:无影响,新表需下游显式引用
  • DWS 层:当前无 DWS 任务引用此表

回滚策略

DROP TABLE IF EXISTS dwd.dwd_goods_stock_movement;
-- 恢复 TABLE_MAP 和 FACT_MAPPINGS移除 dwd.dwd_goods_stock_movement 条目

验证 SQL

-- 1. 确认表已创建
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_movement';

-- 2. 确认列数量正确20 列19 业务 + fetched_at
SELECT count(*) FROM information_schema.columns
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_movement';

-- 3. 确认主键约束
SELECT constraint_name, column_name FROM information_schema.key_column_usage
WHERE table_schema = 'dwd' AND table_name = 'dwd_goods_stock_movement';

-- 4. 确认 ODS 源表存在且有数据
SELECT count(*) FROM ods.goods_stock_movements;

迁移 13新建 DWS 库存汇总表——日/周/月三粒度2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__create_dws_goods_stock_summary.sql

变更说明

在 DWS 层新建三张库存汇总表,基于 dwd.dwd_goods_stock_summary(迁移 11数据按日/周/月三个粒度聚合库存变动指标。

新建表 粒度 stat_period 默认值 任务代码
dws.dws_goods_stock_daily_summary 'daily' DWS_GOODS_STOCK_DAILY
dws.dws_goods_stock_weekly_summary ISO 周stat_date = 周一) 'weekly' DWS_GOODS_STOCK_WEEKLY
dws.dws_goods_stock_monthly_summary 自然月stat_date = 月首) 'monthly' DWS_GOODS_STOCK_MONTHLY

三张表结构完全相同,主键均为 (site_id, stat_date, site_goods_id)

字段定义(三张表共用):

列名 类型 说明
site_id BIGINT NOT NULL 门店 IDPK 组成)
tenant_id BIGINT 租户 ID
stat_date DATE NOT NULL 统计日期PK 组成):日=当天、周=周一、月=月首
site_goods_id BIGINT NOT NULL 门店商品 IDPK 组成)
goods_name TEXT 商品名称
goods_unit TEXT 计量单位
goods_category_id BIGINT 一级分类 ID
goods_category_second_id BIGINT 二级分类 ID
category_name TEXT 分类名称
range_start_stock NUMERIC 期初库存
range_end_stock NUMERIC 期末库存
range_in NUMERIC 入库数量
range_out NUMERIC 出库数量
range_sale NUMERIC 销售数量
range_sale_money NUMERIC(12,2) 销售金额
range_inventory NUMERIC 盘点调整量
current_stock NUMERIC 当前库存(取期末值)
stat_period TEXT NOT NULL 汇总粒度标识
created_at TIMESTAMPTZ NOT NULL 记录创建时间
updated_at TIMESTAMPTZ NOT NULL 记录更新时间

索引(每张表 3 个辅助索引):

索引名模式 用途
idx_dws_goods_stock_{period}_date stat_date 按日期范围查询
idx_dws_goods_stock_{period}_goods (site_goods_id, stat_date) 按商品查询趋势
idx_dws_goods_stock_{period}_site (site_id, stat_date) 按门店查询汇总

任务代码

  • apps/etl/connectors/feiqiu/tasks/dws/goods_stock_daily_task.py
  • apps/etl/connectors/feiqiu/tasks/dws/goods_stock_weekly_task.py
  • apps/etl/connectors/feiqiu/tasks/dws/goods_stock_monthly_task.py

任务继承 BaseDwsTask,实现 extract → transform → load 三阶段load 使用 upsertON CONFLICT DO UPDATE

关联需求

dataflow-field-completion Requirements 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9

兼容性

  • ETL Connector:新增 3 个 DWS 任务,依赖 dwd.dwd_goods_stock_summary 已有数据;不影响现有 DWS 任务
  • 后端 API / 小程序:无影响,新表需下游显式引用
  • DWD 层:只读引用 dwd.dwd_goods_stock_summary,不修改 DWD 数据

回滚策略

DROP TABLE IF EXISTS dws.dws_goods_stock_daily_summary CASCADE;
DROP TABLE IF EXISTS dws.dws_goods_stock_weekly_summary CASCADE;
DROP TABLE IF EXISTS dws.dws_goods_stock_monthly_summary CASCADE;
-- 从任务注册中移除 DWS_GOODS_STOCK_DAILY / DWS_GOODS_STOCK_WEEKLY / DWS_GOODS_STOCK_MONTHLY

验证 SQL

-- 1. 确认三张表已创建
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'dws' AND table_name LIKE 'dws_goods_stock_%_summary'
ORDER BY table_name;
-- 预期3 行daily, monthly, weekly

-- 2. 确认每张表列数量正确20 列)
SELECT table_name, count(*) AS col_count
FROM information_schema.columns
WHERE table_schema = 'dws' AND table_name LIKE 'dws_goods_stock_%_summary'
GROUP BY table_name ORDER BY table_name;

-- 3. 确认主键约束(每张表 3 列主键)
SELECT conrelid::regclass AS table_name,
       array_agg(a.attname ORDER BY k.n) AS pk_columns
FROM pg_constraint c
CROSS JOIN LATERAL unnest(c.conkey) WITH ORDINALITY AS k(col, n)
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.col
WHERE c.contype = 'p'
  AND c.conrelid::regclass::text LIKE 'dws.dws_goods_stock_%_summary'
GROUP BY c.conrelid;

-- 4. 确认辅助索引已创建(每张表 3 个,共 9 个)
SELECT indexname, tablename FROM pg_indexes
WHERE schemaname = 'dws' AND indexname LIKE 'idx_dws_goods_stock_%'
ORDER BY tablename, indexname;

代码变更记录 1recharge_settlements FACT_MAPPINGS 补充2026-02-20

变更说明

dwd.dwd_recharge_order 补充 5 个 FACT_MAPPINGS 条目。这 5 个字段的 DWD 列已存在ODS 源列也已存在,但此前缺少显式映射配置,导致数据未能从 ODS 流转到 DWD。

注意:此变更无 DDL 结构变更,无迁移脚本,仅修改 ETL 代码中的 FACT_MAPPINGS 配置。

DWD 列 ODS 源列 类型 业务含义 数据现状
pl_coupon_sale_amount plcouponsaleamount NUMERIC(18,2) 平台券销售金额 ODS/DWD 两侧数据全为 0业务未启用
mervou_sales_amount mervousalesamount NUMERIC(18,2) 储值券销售金额 同上
electricity_money electricitymoney NUMERIC(18,2) 电费金额 同上
real_electricity_money realelectricitymoney NUMERIC(18,2) 实际电费金额 同上
electricity_adjust_money electricityadjustmoney NUMERIC(18,2) 电费调整金额 同上

代码变更位置apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_recharge_order"]

关联需求

dataflow-field-completion Requirements 6.1, 6.2, 6.3, 6.4

兼容性

  • ETL Connector:重新执行 DWD_LOAD_FROM_ODS 后,这 5 列将从 ODS 正确映射到 DWD当前数据全为 0无实际影响
  • 后端 API / 小程序:无影响
  • DWS 层:当前无 DWS 任务引用这 5 列

代码变更记录 2assistant_cancellation_records assistanton 映射确认2026-02-20

变更说明

确认 dwd.dwd_assistant_trash_eventassistant_no 字段的 FACT_MAPPINGS 映射 ("assistant_no", "assistanton", None) 已正确配置。ODS 列 assistanton(助教编号/工号)映射到 DWD 列 assistant_no

注意:此条目在代码中已存在,本次仅为排查确认,无代码变更,无 DDL 变更。

代码位置apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dwd_assistant_trash_event"]

关联需求

dataflow-field-completion Requirements 3.1, 3.3


迁移 14为 dim_store_goods_ex 新增 batch_stock_quantity 字段2026-02-20

迁移文件

db/etl_feiqiu/migrations/2026-02-20__add_dim_store_goods_ex_batch_stock_quantity.sql

变更说明

dwd.dim_store_goods_ex 新增 1 个字段,映射自 ODS store_goods_master 中的 batch_stock 列。

新增列 类型 业务含义
batch_stock_quantity INTEGER 批次库存数量,记录商品按批次管理的库存量

DDL 结构变更ALTER TABLE ... ADD COLUMN × 1

代码变更apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.pyFACT_MAPPINGS["dwd.dim_store_goods_ex"] 新增 1 个映射条目:

  • ("batch_stock_quantity", "batch_stock", None)ODS 列名 batch_stockDWD 列名 batch_stock_quantity

关联需求

dataflow-field-completion Requirements 10.1, 10.2

兼容性

  • ETL Connector:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新列数据ODS 源列已存在
  • 后端 API / 小程序:新增列不影响现有查询
  • DWS 层:当前无 DWS 任务引用此列,无影响

回滚策略

BEGIN;
ALTER TABLE dwd.dim_store_goods_ex DROP COLUMN IF EXISTS batch_stock_quantity;
COMMIT;

验证 SQL

-- 1. 确认新列已创建且类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dim_store_goods_ex'
  AND column_name = 'batch_stock_quantity';

-- 2. 确认列注释已添加
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods_ex'
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
    (SELECT attnum FROM pg_attribute
     WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'dim_store_goods_ex'
           AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd'))
     AND attname = 'batch_stock_quantity')
) AS batch_stock_quantity_comment;

-- 3. 确认 ODS 源表中 batch_stock 列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name = 'store_goods_master'
  AND column_name = 'batch_stock';

DDL 文件同步记录2026-02-21

变更说明

将迁移 614 的所有结构变更同步到 DDL 源文件,确保 dwd.sql / schema_dwd_doc.sql / dws.sql / schema_dws.sql 与数据库实际结构一致。

同步清单

DDL 文件 变更内容 状态
dwd.sql dim_assistant_ex +4 列system_role_id, job_num, cx_unit_price, pd_unit_price 已有(之前已更新)
dwd.sql dwd_assistant_service_log_ex +2 列operator_id, operator_name 已有(之前已更新)
dwd.sql dwd_member_balance_change_ex +1 列relate_id 已有(之前已更新)
dwd.sql dwd_store_goods_sale discount_price→discount_money 重命名 + 新增 discount_price 已有(之前已更新)
dwd.sql dim_store_goods_ex +1 列batch_stock_quantity 本次补充
dwd.sql dwd_goods_stock_summary 新建表14 列) 已有(之前已更新)
dwd.sql dwd_goods_stock_movement 新建表19 列) 已有(之前已更新)
schema_dwd_doc.sql dim_assistant_ex +4 列 已有sync 脚本已处理)
schema_dwd_doc.sql dim_table_ex +14 列 已有sync 脚本已处理)
schema_dwd_doc.sql dwd_assistant_service_log_ex +2 列operator_id, operator_name 本次补充
schema_dwd_doc.sql dwd_member_balance_change_ex +1 列relate_id 本次补充
schema_dwd_doc.sql dwd_store_goods_sale +1 列discount_money修正 COMMENT 本次补充
schema_dwd_doc.sql dim_store_goods_ex +1 列batch_stock_quantity 本次补充
schema_dwd_doc.sql dwd_goods_stock_summary 新建表 已有sync 脚本已处理)
schema_dwd_doc.sql dwd_goods_stock_movement 新建表 已有sync 脚本已处理)
dws.sql 3 张库存汇总表 新建表 已有(之前已更新)
schema_dws.sql 3 张库存汇总表 新建表 已有sync 脚本已处理)

未同步项(待确认)

字段 说明
(无) 所有已知字段变更均已同步

迁移 15dwd_assistant_trash_event_ex 新增 assistant_no_int 列

  • 日期2026-02-20
  • 脚本:db/etl_feiqiu/migrations/2026-02-20__add_assistant_trash_event_ex_assistant_no_int.sql
  • 执行目标:测试库 test_etl_feiqiu2026-02-21 已执行)

变更说明

变更类型 字段 类型 说明
dwd.dwd_assistant_trash_event_ex ADD COLUMN assistant_no_int INTEGER 助教编号整数形式,与主表 assistant_noVARCHAR同源自 ODS assistanton,便于数值比较和关联

兼容性

  • ETLdwd_load_task.py FACT_MAPPINGS 已同步添加 ("assistant_no_int", "assistanton", None) 映射
  • 后端 API无直接影响当前未查询 _ex 表此字段)
  • 小程序:无影响
  • 新增列允许 NULL不影响现有数据写入

回滚策略

ALTER TABLE dwd.dwd_assistant_trash_event_ex DROP COLUMN IF EXISTS assistant_no_int;

DDL 同步

DDL 文件 变更 状态
dwd.sql dwd_assistant_trash_event_ex +1 列assistant_no_int+ COMMENT 已同步
schema_dwd_doc.sql dwd_assistant_trash_event_ex +1 列assistant_no_int+ COMMENT 已同步

验证 SQL

-- 1. 确认列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_assistant_trash_event_ex'
  AND column_name = 'assistant_no_int';

-- 2. 确认 COMMENT
SELECT col_description(
    (SELECT oid FROM pg_class WHERE relname = 'dwd_assistant_trash_event_ex'),
    (SELECT ordinal_position FROM information_schema.columns
     WHERE table_schema = 'dwd' AND table_name = 'dwd_assistant_trash_event_ex'
       AND column_name = 'assistant_no_int')
);

-- 3. 确认表完整列清单
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'dwd'
  AND table_name = 'dwd_assistant_trash_event_ex'
ORDER BY ordinal_position;