在前后端开发联调前 的提交20260223

This commit is contained in:
Neo
2026-02-23 23:02:20 +08:00
parent 254ccb1e77
commit fafc95e64c
1142 changed files with 10366960 additions and 36957 deletions

View File

@@ -0,0 +1,97 @@
-- 迁移脚本:为 ODS 表添加"取最新版本"复合索引
-- 支持 DISTINCT ON (pk) ORDER BY pk, fetched_at DESC 查询模式
-- 注意CREATE INDEX CONCURRENTLY 不能在事务块内执行,需逐条手动运行或用支持单语句模式的工具
-- 日期2026-02-17
-- 关联需求ods-dedup-standardize Requirements 6.1, 6.2, 6.3
-- 1. assistant_accounts_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_accounts_master_latest
ON ods.assistant_accounts_master (id, fetched_at DESC);
-- 2. settlement_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_settlement_records_latest
ON ods.settlement_records (id, fetched_at DESC);
-- 3. table_fee_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_table_fee_transactions_latest
ON ods.table_fee_transactions (id, fetched_at DESC);
-- 4. assistant_service_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_service_records_latest
ON ods.assistant_service_records (id, fetched_at DESC);
-- 5. assistant_cancellation_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_assistant_cancellation_records_latest
ON ods.assistant_cancellation_records (id, fetched_at DESC);
-- 6. store_goods_sales_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_store_goods_sales_records_latest
ON ods.store_goods_sales_records (id, fetched_at DESC);
-- 7. payment_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_payment_transactions_latest
ON ods.payment_transactions (id, fetched_at DESC);
-- 8. refund_transactions (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_refund_transactions_latest
ON ods.refund_transactions (id, fetched_at DESC);
-- 9. platform_coupon_redemption_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_platform_coupon_redemption_records_latest
ON ods.platform_coupon_redemption_records (id, fetched_at DESC);
-- 10. member_profiles (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_profiles_latest
ON ods.member_profiles (id, fetched_at DESC);
-- 11. member_stored_value_cards (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_stored_value_cards_latest
ON ods.member_stored_value_cards (id, fetched_at DESC);
-- 12. member_balance_changes (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_member_balance_changes_latest
ON ods.member_balance_changes (id, fetched_at DESC);
-- 13. recharge_settlements (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_recharge_settlements_latest
ON ods.recharge_settlements (id, fetched_at DESC);
-- 14. group_buy_packages (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_group_buy_packages_latest
ON ods.group_buy_packages (id, fetched_at DESC);
-- 15. group_buy_redemption_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_group_buy_redemption_records_latest
ON ods.group_buy_redemption_records (id, fetched_at DESC);
-- 16. goods_stock_summary (pk: siteGoodsId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_goods_stock_summary_latest
ON ods.goods_stock_summary (siteGoodsId, fetched_at DESC);
-- 17. goods_stock_movements (pk: siteGoodsStockId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_goods_stock_movements_latest
ON ods.goods_stock_movements (siteGoodsStockId, fetched_at DESC);
-- 18. site_tables_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_site_tables_master_latest
ON ods.site_tables_master (id, fetched_at DESC);
-- 19. stock_goods_category_tree (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_stock_goods_category_tree_latest
ON ods.stock_goods_category_tree (id, fetched_at DESC);
-- 20. store_goods_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_store_goods_master_latest
ON ods.store_goods_master (id, fetched_at DESC);
-- 21. table_fee_discount_records (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_table_fee_discount_records_latest
ON ods.table_fee_discount_records (id, fetched_at DESC);
-- 22. tenant_goods_master (pk: id)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_tenant_goods_master_latest
ON ods.tenant_goods_master (id, fetched_at DESC);
-- 23. settlement_ticket_details (pk: orderSettleId)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ods_settlement_ticket_details_latest
ON ods.settlement_ticket_details (orderSettleId, fetched_at DESC);

View File

@@ -0,0 +1,67 @@
-- 迁移:为 dwd_assistant_service_log_ex 新增 operator_id、operator_name 两列
-- 关联需求dataflow-field-completion Requirements 2.1, 2.2
-- 日期2026-02-20
BEGIN;
-- 1. 新增列
ALTER TABLE dwd.dwd_assistant_service_log_ex
ADD COLUMN IF NOT EXISTS operator_id BIGINT;
ALTER TABLE dwd.dwd_assistant_service_log_ex
ADD COLUMN IF NOT EXISTS operator_name TEXT;
-- 2. 列注释
COMMENT ON COLUMN dwd.dwd_assistant_service_log_ex.operator_id IS
'【说明】操作员 ID录入/结算这条助教服务的员工。 【ODS来源】assistant_service_records - operator_id。 【JSON字段】assistant_service_records.json - data.orderAssistantDetails - operator_id。';
COMMENT ON COLUMN dwd.dwd_assistant_service_log_ex.operator_name IS
'【说明】操作员姓名(带职位前缀),与 operator_id 一起使用,便于直接阅读。 【ODS来源】assistant_service_records - operator_name。 【JSON字段】assistant_service_records.json - data.orderAssistantDetails - operator_name。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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;

View File

@@ -0,0 +1,14 @@
-- 迁移:为 dwd_assistant_trash_event_ex 新增 assistant_no_int 列
-- 背景:主表 assistant_no (VARCHAR) 映射自 ODS assistanton存储的是数字编号的字符串形式。
-- 新增 INTEGER 类型列便于数值比较和关联助教档案。
-- 日期2026-02-20
BEGIN;
ALTER TABLE dwd.dwd_assistant_trash_event_ex
ADD COLUMN IF NOT EXISTS assistant_no_int INTEGER;
COMMENT ON COLUMN dwd.dwd_assistant_trash_event_ex.assistant_no_int IS
'【说明】助教编号(整数形式),与主表 assistant_noVARCHAR同源但类型不同便于数值比较和关联。 【示例】6。 【ODS来源】assistant_cancellation_records - assistanton。 【JSON字段】assistant_cancellation_records.json - data.abolitionAssistants - assistantOn。';
COMMIT;

View File

@@ -0,0 +1,71 @@
-- 迁移:为 dwd.dim_assistant_ex 新增 4 个字段
-- 来源assistant_accounts_master ODS 表中已有同名列,直接映射
-- 关联需求dataflow-field-completion Requirements 1.1, 1.2, 1.3
BEGIN;
-- 1. 新增列
ALTER TABLE dwd.dim_assistant_ex ADD COLUMN IF NOT EXISTS system_role_id BIGINT;
ALTER TABLE dwd.dim_assistant_ex ADD COLUMN IF NOT EXISTS job_num TEXT;
ALTER TABLE dwd.dim_assistant_ex ADD COLUMN IF NOT EXISTS cx_unit_price NUMERIC(18,2);
ALTER TABLE dwd.dim_assistant_ex ADD COLUMN IF NOT EXISTS pd_unit_price NUMERIC(18,2);
-- 2. 添加列注释
COMMENT ON COLUMN dwd.dim_assistant_ex.system_role_id IS
'【说明】系统角色 ID标识助教在系统中的角色类型。 【ODS来源】assistant_accounts_master - system_role_id。 【JSON字段】assistant_accounts_master.json - data.assistantInfos - system_role_id。';
COMMENT ON COLUMN dwd.dim_assistant_ex.job_num IS
'【说明】工号,助教的内部编号标识。 【ODS来源】assistant_accounts_master - job_num。 【JSON字段】assistant_accounts_master.json - data.assistantInfos - job_num。';
COMMENT ON COLUMN dwd.dim_assistant_ex.cx_unit_price IS
'【说明】促销单价(元),助教提供促销服务时的计费单价。 【ODS来源】assistant_accounts_master - cx_unit_price。 【JSON字段】assistant_accounts_master.json - data.assistantInfos - cx_unit_price。';
COMMENT ON COLUMN dwd.dim_assistant_ex.pd_unit_price IS
'【说明】陪打单价(元),助教提供陪打服务时的计费单价。 【ODS来源】assistant_accounts_master - pd_unit_price。 【JSON字段】assistant_accounts_master.json - data.assistantInfos - pd_unit_price。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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;

View File

@@ -0,0 +1,49 @@
-- 迁移:为 dwd.dim_store_goods_ex 新增 batch_stock_quantity 列
-- 日期2026-02-20
-- 关联需求dataflow-field-completionNEW_FIELDS 中定义但缺少迁移脚本)
-- 说明:批次库存数量,来源 ODS store_goods_master.batch_stock_quantity
BEGIN;
ALTER TABLE dwd.dim_store_goods_ex
ADD COLUMN IF NOT EXISTS batch_stock_quantity NUMERIC;
COMMENT ON COLUMN dwd.dim_store_goods_ex.batch_stock_quantity IS
'【说明】批次库存数量区别于当前库存stock和主表的 batch_stock_qty。 【ODS来源】store_goods_master - batch_stock_quantity。 【JSON字段】store_goods_master.json - data.orderGoodsList - batchStockQuantity。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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 源表中同名列存在
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'ods'
-- AND table_name = 'store_goods_master'
-- AND column_name = 'batch_stock_quantity';

View File

@@ -0,0 +1,129 @@
-- 迁移:为 dwd.dim_table_ex 新增 14 个字段
-- 来源site_tables_master ODS 表中已有同名列(部分驼峰式在 PG 中已小写化)
-- 关联需求dataflow-field-completion Requirements 9.1, 9.2
BEGIN;
-- 1. 新增列
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS create_time TIMESTAMPTZ;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS light_status INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS tablestatusname TEXT;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS sitename TEXT;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS applet_qr_code_url TEXT;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS audit_status INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS charge_free INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS delay_lights_time INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS is_rest_area INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS only_allow_groupon INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS order_delay_time INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS self_table INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS temporary_light_second INTEGER;
ALTER TABLE dwd.dim_table_ex ADD COLUMN IF NOT EXISTS virtual_table INTEGER;
-- 2. 添加列注释
COMMENT ON COLUMN dwd.dim_table_ex.create_time IS
'【说明】台桌配置的创建时间或最近一次创建/复制时间。 【示例】2025-07-15 17:52:54。 【ODS来源】site_tables_master - create_time。 【JSON字段】site_tables_master.json - data.siteTables - create_time。';
COMMENT ON COLUMN dwd.dim_table_ex.light_status IS
'【说明】台灯状态枚举(如 2=已开灯),用于标识台桌灯光当前状态。 【示例】2。 【ODS来源】site_tables_master - light_status。 【JSON字段】site_tables_master.json - data.siteTables - light_status。';
COMMENT ON COLUMN dwd.dim_table_ex.tablestatusname IS
'【说明】台桌状态中文名称(如"空闲中""使用中"),仅展示用途。 【示例】空闲中。 【ODS来源】site_tables_master - tablestatusname。 【JSON字段】site_tables_master.json - data.siteTables - tableStatusName。';
COMMENT ON COLUMN dwd.dim_table_ex.sitename IS
'【说明】门店名称快照,冗余字段,配合 site_id 使用。 【示例】朗朗桌球。 【ODS来源】site_tables_master - sitename。 【JSON字段】site_tables_master.json - data.siteTables - siteName。';
COMMENT ON COLUMN dwd.dim_table_ex.applet_qr_code_url IS
'【说明】小程序二维码 URL用于扫码开台等场景。 【ODS来源】site_tables_master - appletQrCodeUrl。 【JSON字段】site_tables_master.json - data.siteTables - appletQrCodeUrl。';
COMMENT ON COLUMN dwd.dim_table_ex.audit_status IS
'【说明】审核状态枚举(当前全部为 2含义待确认。 【示例】2。 【ODS来源】site_tables_master - audit_status。 【JSON字段】site_tables_master.json - data.siteTables - audit_status。';
COMMENT ON COLUMN dwd.dim_table_ex.charge_free IS
'【说明】是否免费台0=收费1=免费),当前全部为 0。 【示例】0。 【ODS来源】site_tables_master - charge_free。 【JSON字段】site_tables_master.json - data.siteTables - charge_free。';
COMMENT ON COLUMN dwd.dim_table_ex.delay_lights_time IS
'【说明】台灯熄灭延迟时间(单位秒或分钟),结账后延时关灯。 【示例】0。 【ODS来源】site_tables_master - delay_lights_time。 【JSON字段】site_tables_master.json - data.siteTables - delay_lights_time。';
COMMENT ON COLUMN dwd.dim_table_ex.is_rest_area IS
'【说明】是否休息区台桌0=否1=是),当前全部为 0。 【示例】0。 【ODS来源】site_tables_master - is_rest_area。 【JSON字段】site_tables_master.json - data.siteTables - is_rest_area。';
COMMENT ON COLUMN dwd.dim_table_ex.only_allow_groupon IS
'【说明】是否仅允许团购开台0/1/2 枚举)。 【示例】2。 【ODS来源】site_tables_master - only_allow_groupon。 【JSON字段】site_tables_master.json - data.siteTables - only_allow_groupon。';
COMMENT ON COLUMN dwd.dim_table_ex.order_delay_time IS
'【说明】订单自动延时时长(到点后自动延长继续计费的时间)。 【示例】0。 【ODS来源】site_tables_master - order_delay_time。 【JSON字段】site_tables_master.json - data.siteTables - order_delay_time。';
COMMENT ON COLUMN dwd.dim_table_ex.self_table IS
'【说明】是否自有台桌1=自有),当前全部为 1。 【示例】1。 【ODS来源】site_tables_master - self_table。 【JSON字段】site_tables_master.json - data.siteTables - self_table。';
COMMENT ON COLUMN dwd.dim_table_ex.temporary_light_second IS
'【说明】临时开灯秒数,用于短时照明场景。 【示例】0。 【ODS来源】site_tables_master - temporary_light_second。 【JSON字段】site_tables_master.json - data.siteTables - temporary_light_second。';
COMMENT ON COLUMN dwd.dim_table_ex.virtual_table IS
'【说明】是否虚拟台桌0=实体台1=虚拟台)。 【示例】0。 【ODS来源】site_tables_master - virtual_table。 【JSON字段】site_tables_master.json - data.siteTables - virtual_table。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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, delay_lights_time,
-- is_rest_area, only_allow_groupon, order_delay_time, self_table,
-- temporary_light_second, virtual_table
-- FROM dwd.dim_table_ex
-- WHERE scd2_is_current = 1
-- LIMIT 10;

View File

@@ -0,0 +1,57 @@
-- 迁移:为 dwd_member_balance_change_ex 新增 relate_id 列
-- 关联需求dataflow-field-completion Requirements 5.1, 5.2
-- 说明relate_id 为关联业务单据 ID指向触发余额变动的业务记录充值单、订单等
-- ODS 源列ods.member_balance_changes.relate_idBIGINT已存在
BEGIN;
-- 新增列
ALTER TABLE dwd.dwd_member_balance_change_ex
ADD COLUMN IF NOT EXISTS relate_id BIGINT;
-- 添加列注释
COMMENT ON COLUMN dwd.dwd_member_balance_change_ex.relate_id IS
'【说明】关联业务单据 ID指向触发本次余额变动的业务记录如充值单、订单、结算单等按 from_type 不同指向不同表。 【示例】2957881518788421。 【ODS来源】member_balance_changes - relate_id。 【JSON字段】member_balance_changes.json - data.tenantMemberCardLogs - relate_id。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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;

View File

@@ -0,0 +1,34 @@
-- 迁移:新建 dwd.dwd_goods_stock_movement 表
-- 日期2026-02-20
-- 原因:为库存变动流水创建 DWD 层明细表,按 create_time 增量加载
-- 关联任务dataflow-field-completion Task 6.2
BEGIN;
CREATE TABLE IF NOT EXISTS dwd.dwd_goods_stock_movement (
site_goods_stock_id BIGINT NOT NULL,
tenant_id BIGINT,
site_id BIGINT,
site_goods_id BIGINT,
goods_name TEXT,
goods_category_id BIGINT,
goods_second_category_id BIGINT,
unit TEXT,
price NUMERIC(18,4),
stock_type INTEGER,
change_num NUMERIC(18,4),
start_num NUMERIC(18,4),
end_num NUMERIC(18,4),
change_num_a NUMERIC(18,4),
start_num_a NUMERIC(18,4),
end_num_a NUMERIC(18,4),
remark TEXT,
operator_name TEXT,
create_time TIMESTAMPTZ,
fetched_at TIMESTAMPTZ,
PRIMARY KEY (site_goods_stock_id)
);
COMMENT ON TABLE dwd.dwd_goods_stock_movement IS '库存变动流水表事实表。来源ods.goods_stock_movements。按 create_time 增量加载。';
COMMIT;

View File

@@ -0,0 +1,31 @@
-- 迁移:新建 dwd.dwd_goods_stock_summary 表
-- 日期2026-02-20
-- 原因:为库存汇总数据创建 DWD 层明细表,支持按时间窗口增量加载
-- 关联任务dataflow-field-completion Task 6.1
BEGIN;
CREATE TABLE IF NOT EXISTS dwd.dwd_goods_stock_summary (
site_goods_id BIGINT NOT NULL,
goods_name TEXT,
goods_unit TEXT,
goods_category_id BIGINT,
goods_category_second_id BIGINT,
category_name TEXT,
range_start_stock NUMERIC(18,4),
range_end_stock NUMERIC(18,4),
range_in NUMERIC(18,4),
range_out NUMERIC(18,4),
range_sale NUMERIC(18,4),
range_sale_money NUMERIC(18,2),
range_inventory NUMERIC(18,4),
current_stock NUMERIC(18,4),
site_id BIGINT,
tenant_id BIGINT,
fetched_at TIMESTAMPTZ,
PRIMARY KEY (site_goods_id, fetched_at)
);
COMMENT ON TABLE dwd.dwd_goods_stock_summary IS '库存汇总明细表事实表。来源ods.goods_stock_summary。按时间窗口增量加载。';
COMMIT;

View File

@@ -0,0 +1,147 @@
-- =============================================================================
-- 迁移脚本:创建 DWS 库存汇总表(日/周/月)
-- 日期: 2026-02-20
-- 依赖: dwd.dwd_goods_stock_summary 已存在
-- 说明: 三张表结构相同,仅 stat_period 默认值不同
-- 回滚: 见文件末尾 ROLLBACK 段
-- =============================================================================
BEGIN;
-- -----------------------------------------------------------------------------
-- 1. 日度汇总
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dws.dws_goods_stock_daily_summary (
site_id BIGINT NOT NULL,
tenant_id BIGINT,
stat_date DATE NOT NULL,
site_goods_id BIGINT NOT NULL,
goods_name TEXT,
goods_unit TEXT,
goods_category_id BIGINT,
goods_category_second_id BIGINT,
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 DEFAULT 'daily',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (site_id, stat_date, site_goods_id)
);
COMMENT ON TABLE dws.dws_goods_stock_daily_summary
IS '库存日度汇总:按门店+日期+商品汇总库存变动';
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_daily_date
ON dws.dws_goods_stock_daily_summary (stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_daily_goods
ON dws.dws_goods_stock_daily_summary (site_goods_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_daily_site
ON dws.dws_goods_stock_daily_summary (site_id, stat_date);
-- -----------------------------------------------------------------------------
-- 2. 周度汇总
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dws.dws_goods_stock_weekly_summary (
site_id BIGINT NOT NULL,
tenant_id BIGINT,
stat_date DATE NOT NULL,
site_goods_id BIGINT NOT NULL,
goods_name TEXT,
goods_unit TEXT,
goods_category_id BIGINT,
goods_category_second_id BIGINT,
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 DEFAULT 'weekly',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (site_id, stat_date, site_goods_id)
);
COMMENT ON TABLE dws.dws_goods_stock_weekly_summary
IS '库存周度汇总:按门店+ISO周+商品汇总库存变动stat_date 为周一日期';
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_weekly_date
ON dws.dws_goods_stock_weekly_summary (stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_weekly_goods
ON dws.dws_goods_stock_weekly_summary (site_goods_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_weekly_site
ON dws.dws_goods_stock_weekly_summary (site_id, stat_date);
-- -----------------------------------------------------------------------------
-- 3. 月度汇总
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dws.dws_goods_stock_monthly_summary (
site_id BIGINT NOT NULL,
tenant_id BIGINT,
stat_date DATE NOT NULL,
site_goods_id BIGINT NOT NULL,
goods_name TEXT,
goods_unit TEXT,
goods_category_id BIGINT,
goods_category_second_id BIGINT,
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 DEFAULT 'monthly',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (site_id, stat_date, site_goods_id)
);
COMMENT ON TABLE dws.dws_goods_stock_monthly_summary
IS '库存月度汇总:按门店+自然月+商品汇总库存变动stat_date 为月首日期';
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_monthly_date
ON dws.dws_goods_stock_monthly_summary (stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_monthly_goods
ON dws.dws_goods_stock_monthly_summary (site_goods_id, stat_date);
CREATE INDEX IF NOT EXISTS idx_dws_goods_stock_monthly_site
ON dws.dws_goods_stock_monthly_summary (site_id, stat_date);
COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认三张表已创建
-- SELECT table_name FROM information_schema.tables
-- WHERE table_schema = 'dws' AND table_name LIKE 'dws_goods_stock_%_summary';
--
-- 2. 确认主键约束
-- SELECT conname, conrelid::regclass
-- FROM pg_constraint
-- WHERE conrelid::regclass::text LIKE 'dws.dws_goods_stock_%_summary'
-- AND contype = 'p';
--
-- 3. 确认索引数量(每张表 3 个 + 1 个主键 = 4
-- SELECT indexrelid::regclass, indrelid::regclass
-- FROM pg_index
-- WHERE indrelid::regclass::text LIKE 'dws.dws_goods_stock_%_summary';
-- =============================================================================
-- ROLLBACK回滚脚本
-- =============================================================================
-- 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;

View File

@@ -0,0 +1,62 @@
-- ============================================================
-- 迁移脚本:修正 dwd_assistant_service_log.site_assistant_id 映射源
-- 日期2026-02-20
-- 问题site_assistant_id 错误映射自 ODS order_assistant_id订单级助教明细 ID
-- 应映射自 ODS site_assistant_id门店维度助教档案 ID
-- 修正:
-- 1. FACT_MAPPINGS 中 site_assistant_id 的 ODS 源从 order_assistant_id 改为 site_assistant_id
-- 2. order_assistant_id 列已存在于 DWD 表中,由同名列自动映射,无需 DDL 变更
-- 3. 更新 COMMENT 以反映正确的 ODS 来源
-- 后续操作:需重新执行 DWD_LOAD_FROM_ODS 任务以修正历史数据
-- ============================================================
BEGIN;
-- 更新 site_assistant_id 的列注释,修正 ODS 来源说明
COMMENT ON COLUMN dwd.dwd_assistant_service_log.site_assistant_id IS
'【说明】门店维度的助教档案 ID关联 assistant_accounts_master.id。'
' 【示例】2946266869435205。'
' 【ODS来源】assistant_service_records - site_assistant_id。'
' 【JSON字段】assistant_service_records.json - data.orderAssistantDetails - site_assistant_id。';
-- 更新 order_assistant_id 的列注释,明确其为订单级 ID
COMMENT ON COLUMN dwd.dwd_assistant_service_log.order_assistant_id IS
'【说明】订单中助教项目明细的内部 ID订单级与 site_assistant_id档案级不同。'
' 【示例】2957788717240005。'
' 【ODS来源】assistant_service_records - order_assistant_id。'
' 【JSON字段】assistant_service_records.json - data.orderAssistantDetails - order_assistant_id。';
COMMIT;
-- ============================================================
-- 回滚脚本(如需撤销):
-- BEGIN;
-- -- 恢复原始 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。';
-- COMMIT;
-- ============================================================
-- ============================================================
-- 验证 SQL迁移后执行
--
-- 1. 确认 site_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 = '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 与 order_assistant_id 值不同(抽样)
-- SELECT site_assistant_id, order_assistant_id,
-- (site_assistant_id = order_assistant_id) AS same
-- FROM dwd.dwd_assistant_service_log
-- LIMIT 20;
-- ============================================================

View File

@@ -0,0 +1,85 @@
-- ============================================================
-- 迁移:修正 dim_store_goods.batch_stock_qty 和
-- dim_store_goods_ex.provisional_total_cost 的列注释
-- 日期2026-02-20
-- 关联需求dataflow-field-completion Requirements 10.3
-- ============================================================
-- 变更说明:
-- 本次仅修正列 COMMENT无 DDL 结构变更(列已存在)。
-- batch_stock_qty 的 ODS 来源从 stock 修正为 batch_stock_quantity
-- provisional_total_cost 的 ODS 来源从 total_purchase_cost 修正为 provisional_total_cost
--
-- 代码侧变更(不在本脚本范围):
-- dwd_load_task.py FACT_MAPPINGS 已同步修正
-- 修正后需重新执行 DWD_LOAD_FROM_ODS 以回填历史数据
-- ============================================================
BEGIN;
-- 1. 修正 batch_stock_qty 注释ODS 来源 stock → batch_stock_quantity
COMMENT ON COLUMN dwd.dim_store_goods.batch_stock_qty IS
'【说明】批次库存数量区别于当前库存stock。 【示例】18。 【ODS来源】store_goods_master - batch_stock_quantity。 【JSON字段】store_goods_master.json - data.orderGoodsList - batchStockQuantity。';
-- 2. 修正 provisional_total_cost 注释ODS 来源 total_purchase_cost → provisional_total_cost
COMMENT ON COLUMN dwd.dim_store_goods_ex.provisional_total_cost IS
'【说明】暂估总成本区别于实际采购成本total_purchase_cost。 【示例】0.0。 【ODS来源】store_goods_master - provisional_total_cost。 【JSON字段】store_goods_master.json - data.orderGoodsList - provisionalTotalCost。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- 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;
-- 预期:包含 'batch_stock_quantity',不包含 '- stock。'
-- 2. 确认 provisional_total_cost 注释已更新为 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;
-- 预期:包含 'provisional_total_cost',不包含 'total_purchase_cost'
-- 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;
-- 预期batch_stock_qty → integer; provisional_total_cost → numeric(18,2)
-- 4. 重新加载后抽样验证 batch_stock_qty 来自 batch_stock_quantity非 stock
-- 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;

View File

@@ -0,0 +1,79 @@
-- ============================================================
-- 迁移脚本:修正 dwd_store_goods_sale.discount_price 列名误导
-- 日期2026-02-20
-- 问题DWD discount_price 实际映射自 ODS discount_money折扣金额
-- 而非 ODS discount_price折后单价列名存在语义误导。
-- 修正:
-- 1. 将 DWD 列 discount_price 重命名为 discount_money反映真实语义折扣金额
-- 2. 新增 DWD 列 discount_pricenumeric映射 ODS discount_price折后单价
-- 3. 更新 COMMENT 以反映正确的语义和 ODS 来源
-- 后续操作:需重新执行 DWD_LOAD_FROM_ODS 任务以填充新 discount_price 列
-- ============================================================
BEGIN;
-- 步骤 1将现有 discount_price 列重命名为 discount_money
ALTER TABLE dwd.dwd_store_goods_sale
RENAME COLUMN discount_price TO discount_money;
-- 步骤 2新增 discount_price 列(折后单价)
ALTER TABLE dwd.dwd_store_goods_sale
ADD COLUMN discount_price NUMERIC(18,2);
-- 步骤 3更新列注释
COMMENT ON COLUMN dwd.dwd_store_goods_sale.discount_money IS
'【说明】折扣金额,即原价被减免的金额部分。'
' 【示例】0.0。'
' 【ODS来源】store_goods_sales_records - discount_money。'
' 【JSON字段】store_goods_sales_records.json - data.orderGoodsLedgers - discount_money。';
COMMENT ON COLUMN dwd.dwd_store_goods_sale.discount_price IS
'【说明】折后单价(元/单位),即应用折扣后的商品单价。'
' 【示例】5.00。'
' 【ODS来源】store_goods_sales_records - discount_price。'
' 【JSON字段】store_goods_sales_records.json - data.orderGoodsLedgers - discount_price。';
COMMIT;
-- ============================================================
-- 回滚脚本(如需撤销):
-- 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. 重新加载后抽样验证 discount_money 与 discount_price 值不同
-- 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;
-- ============================================================

View File

@@ -0,0 +1,8 @@
-- 移除 settlement_ticket_details 表和索引
-- 该表已从 ETL 流程中彻底移除,不再使用
DROP INDEX IF EXISTS ods.idx_ods_settlement_ticket_details_latest;
DROP TABLE IF EXISTS ods.settlement_ticket_details;
-- 移除 meta.ods_task_registry 中的任务注册
DELETE FROM meta.ods_task_registry WHERE task_code = 'ODS_SETTLEMENT_TICKET';

View File

@@ -0,0 +1,87 @@
-- 迁移:新增 time_slot_sale 字段 + 合并 commodity_code 为数组类型
-- 日期2026-02-21
-- 关联需求dataflow 字段差异修复time_slot_sale 未映射 + commoditycode 合并)
--
-- 变更 1: ods.store_goods_master 新增 time_slot_saleAPI 返回但 ODS 未收录)
-- 变更 2: dwd.dim_store_goods_ex 新增 time_slot_saleODS→DWD 传递)
-- 变更 3: dwd.dim_tenant_goods_ex.commodity_code_list 改为 TEXT[](支持多编码数组)
BEGIN;
-- ============================================================
-- 变更 1: ODS 层 — store_goods_master 新增 time_slot_sale
-- ============================================================
ALTER TABLE ods.store_goods_master
ADD COLUMN IF NOT EXISTS time_slot_sale INTEGER;
COMMENT ON COLUMN ods.store_goods_master.time_slot_sale IS
'【说明】分时段销售标记API 返回值,当前观测全部为 2。 【示例】2。 【JSON字段】store_goods_master.json - data.orderGoodsList - time_slot_sale。';
-- ============================================================
-- 变更 2: DWD 层 — dim_store_goods_ex 新增 time_slot_sale
-- ============================================================
ALTER TABLE dwd.dim_store_goods_ex
ADD COLUMN IF NOT EXISTS time_slot_sale INTEGER;
COMMENT ON COLUMN dwd.dim_store_goods_ex.time_slot_sale IS
'【说明】分时段销售标记(当前观测全部为 2。 【ODS来源】store_goods_master - time_slot_sale。 【JSON字段】store_goods_master.json - data.orderGoodsList - time_slot_sale。';
-- ============================================================
-- 变更 3: DWD 层 — dim_tenant_goods_ex.commodity_code_list 改为 TEXT[]
-- ============================================================
-- 先将现有 VARCHAR 数据迁移到数组类型
ALTER TABLE dwd.dim_tenant_goods_ex
ALTER COLUMN commodity_code_list TYPE TEXT[]
USING CASE
WHEN commodity_code_list IS NULL THEN NULL
WHEN commodity_code_list = '' THEN '{}'::TEXT[]
ELSE ARRAY[commodity_code_list]
END;
COMMENT ON COLUMN dwd.dim_tenant_goods_ex.commodity_code_list IS
'【说明】商品编码数组(合并自 ODS commodityCode 数组字段)。单值时为单元素数组。 【ODS来源】tenant_goods_master - commodityCodeJSON 数组格式,如 ["10000028"])。 【JSON字段】tenant_goods_master.json - data.tenantGoodsList - commodityCode。';
COMMIT;
-- ============================================================
-- 回滚策略
-- ============================================================
-- BEGIN;
-- ALTER TABLE ods.store_goods_master DROP COLUMN IF EXISTS time_slot_sale;
-- ALTER TABLE dwd.dim_store_goods_ex DROP COLUMN IF EXISTS time_slot_sale;
-- ALTER TABLE dwd.dim_tenant_goods_ex
-- ALTER COLUMN commodity_code_list TYPE VARCHAR(256)
-- USING CASE
-- WHEN commodity_code_list IS NULL THEN NULL
-- WHEN array_length(commodity_code_list, 1) IS NULL THEN ''
-- ELSE commodity_code_list[1]
-- END;
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认 ODS time_slot_sale 列已创建
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'ods' AND table_name = 'store_goods_master'
-- AND column_name = 'time_slot_sale';
-- 2. 确认 DWD time_slot_sale 列已创建
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'dwd' AND table_name = 'dim_store_goods_ex'
-- AND column_name = 'time_slot_sale';
-- 3. 确认 commodity_code_list 类型已变更为 ARRAY
-- SELECT column_name, data_type, udt_name
-- FROM information_schema.columns
-- WHERE table_schema = 'dwd' AND table_name = 'dim_tenant_goods_ex'
-- AND column_name = 'commodity_code_list';
-- 4. 确认现有 commodity_code_list 数据已正确迁移
-- SELECT tenant_goods_id, commodity_code_list
-- FROM dwd.dim_tenant_goods_ex
-- WHERE scd2_is_current = 1 AND commodity_code_list IS NOT NULL
-- LIMIT 5;

View File

@@ -0,0 +1,78 @@
-- 迁移dws_assistant_monthly_summary 唯一约束变更
-- 日期2026-02-22
-- 关联:需求 A需求 2.2)— 助教月度聚合按档位分段统计
-- 影响dws.dws_assistant_monthly_summary 唯一约束从 (site_id, assistant_id, stat_month)
-- 变更为 (site_id, assistant_id, stat_month, assistant_level_code)
-- 原因:助教月内可能因升级/降级而存在多个 assistant_level_code
-- 需按档位分段统计业绩,旧约束不允许同一助教同月多行记录
-- 幂等DROP IF EXISTS + 先检查再 ADD可重复执行
BEGIN;
-- ============================================================
-- 1. 删除旧唯一约束
-- ============================================================
ALTER TABLE dws.dws_assistant_monthly_summary
DROP CONSTRAINT IF EXISTS uk_dws_assistant_monthly;
-- ============================================================
-- 2. 创建新唯一约束(加入 assistant_level_code
-- ============================================================
ALTER TABLE dws.dws_assistant_monthly_summary
ADD CONSTRAINT uk_dws_assistant_monthly
UNIQUE (site_id, assistant_id, stat_month, assistant_level_code);
COMMIT;
-- ============================================================
-- 回滚(需手动执行,不在事务内)
-- ============================================================
-- ⚠️ 回滚前须先清理同一 (site_id, assistant_id, stat_month) 的多行数据,
-- 否则恢复旧约束会因重复键失败。
--
-- 步骤 1清理多档位数据保留每组最新一条
-- DELETE FROM dws.dws_assistant_monthly_summary a
-- USING (
-- SELECT site_id, assistant_id, stat_month,
-- MAX(updated_at) AS keep_updated_at
-- FROM dws.dws_assistant_monthly_summary
-- GROUP BY site_id, assistant_id, stat_month
-- ) b
-- WHERE a.site_id = b.site_id
-- AND a.assistant_id = b.assistant_id
-- AND a.stat_month = b.stat_month
-- AND a.updated_at <> b.keep_updated_at;
--
-- 步骤 2恢复旧约束
-- BEGIN;
-- ALTER TABLE dws.dws_assistant_monthly_summary
-- DROP CONSTRAINT IF EXISTS uk_dws_assistant_monthly;
-- ALTER TABLE dws.dws_assistant_monthly_summary
-- ADD CONSTRAINT uk_dws_assistant_monthly
-- UNIQUE (site_id, assistant_id, stat_month);
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认新约束存在且列组合正确
-- SELECT conname, pg_get_constraintdef(oid) AS constraint_def
-- FROM pg_constraint
-- WHERE conrelid = 'dws.dws_assistant_monthly_summary'::regclass
-- AND conname = 'uk_dws_assistant_monthly';
-- 预期1 行constraint_def 包含 (site_id, assistant_id, stat_month, assistant_level_code)
-- 2. 确认约束列数为 4
-- SELECT COUNT(*) AS col_count
-- FROM pg_constraint c
-- JOIN LATERAL unnest(c.conkey) AS col_num ON TRUE
-- WHERE c.conrelid = 'dws.dws_assistant_monthly_summary'::regclass
-- AND c.conname = 'uk_dws_assistant_monthly';
-- 预期4
-- 3. 确认旧的 3 列约束不存在(无同名但只有 3 列的约束)
-- SELECT conname, array_length(conkey, 1) AS col_count
-- FROM pg_constraint
-- WHERE conrelid = 'dws.dws_assistant_monthly_summary'::regclass
-- AND contype = 'u';
-- 预期uk_dws_assistant_monthly 的 col_count = 4非 3

View File

@@ -0,0 +1,51 @@
-- =============================================================================
-- 迁移脚本dws_assistant_salary_calc 唯一约束变更
-- 说明:配合档位分段工资计算,唯一键加入 assistant_level_code
-- 日期2026-02-22
-- 关联需求:需求 A助教月度聚合按档位分段统计— 任务 7.4
-- =============================================================================
-- ---- 正向迁移 ----
-- 1. 删除旧唯一约束
ALTER TABLE dws.dws_assistant_salary_calc
DROP CONSTRAINT IF EXISTS uk_dws_assistant_salary;
-- 2. 创建新唯一约束(加入 assistant_level_code
ALTER TABLE dws.dws_assistant_salary_calc
ADD CONSTRAINT uk_dws_assistant_salary
UNIQUE (site_id, assistant_id, salary_month, assistant_level_code);
-- ---- 回滚 ----
-- ALTER TABLE dws.dws_assistant_salary_calc
-- DROP CONSTRAINT IF EXISTS uk_dws_assistant_salary;
-- ALTER TABLE dws.dws_assistant_salary_calc
-- ADD CONSTRAINT uk_dws_assistant_salary
-- UNIQUE (site_id, assistant_id, salary_month);
-- ---- 验证 SQL ----
-- 1. 确认新约束存在且包含 4 列
-- SELECT conname, array_agg(a.attname ORDER BY x.n)
-- FROM pg_constraint c
-- JOIN pg_class r ON r.oid = c.conrelid
-- JOIN pg_namespace ns ON ns.oid = r.relnamespace
-- CROSS JOIN LATERAL unnest(c.conkey) WITH ORDINALITY AS x(attnum, n)
-- JOIN pg_attribute a ON a.attrelid = r.oid AND a.attnum = x.attnum
-- WHERE ns.nspname = 'dws' AND r.relname = 'dws_assistant_salary_calc'
-- AND c.contype = 'u'
-- GROUP BY conname;
-- 预期: uk_dws_assistant_salary | {site_id,assistant_id,salary_month,assistant_level_code}
-- 2. 确认旧约束不存在(只有一个唯一约束)
-- SELECT count(*) FROM pg_constraint c
-- JOIN pg_class r ON r.oid = c.conrelid
-- JOIN pg_namespace ns ON ns.oid = r.relnamespace
-- WHERE ns.nspname = 'dws' AND r.relname = 'dws_assistant_salary_calc'
-- AND c.contype = 'u';
-- 预期: 1
-- 3. 测试插入同一助教同月不同档位不冲突
-- INSERT INTO dws.dws_assistant_salary_calc (site_id, tenant_id, assistant_id, salary_month, assistant_level_code)
-- VALUES (1, 1, 100, '2026-01-01', 10), (1, 1, 100, '2026-01-01', 20);
-- 预期: 成功插入 2 行
-- DELETE FROM dws.dws_assistant_salary_calc WHERE site_id = 1 AND assistant_id = 100 AND salary_month = '2026-01-01';

View File

@@ -0,0 +1,58 @@
-- 迁移ODS/DWD 层会员表新增 birthday 列
-- 日期2026-02-22
-- 关联:需求 C1需求 4.1)— 会员生日字段 ETL 链路补齐
-- 影响ods.member_profiles 加列、dwd.dim_member 加列
-- 幂等ADD COLUMN IF NOT EXISTS可重复执行
BEGIN;
-- ============================================================
-- 1. ODS 层member_profiles 加 birthday 列
-- ============================================================
ALTER TABLE ods.member_profiles
ADD COLUMN IF NOT EXISTS birthday DATE;
COMMENT ON COLUMN ods.member_profiles.birthday
IS '会员生日,来源:上游 API payload 中的 birthday 字段';
-- ============================================================
-- 2. DWD 层dim_member 加 birthday 列
-- ============================================================
ALTER TABLE dwd.dim_member
ADD COLUMN IF NOT EXISTS birthday DATE;
COMMENT ON COLUMN dwd.dim_member.birthday
IS '会员生日来源ODS member_profiles payload 中的 birthday 字段';
COMMIT;
-- ============================================================
-- 回滚(需手动执行,不在事务内)
-- ============================================================
-- BEGIN;
-- ALTER TABLE ods.member_profiles DROP COLUMN IF EXISTS birthday;
-- ALTER TABLE dwd.dim_member DROP COLUMN IF EXISTS birthday;
-- COMMIT;
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认 ods.member_profiles.birthday 列存在
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'ods'
-- AND table_name = 'member_profiles'
-- AND column_name = 'birthday';
-- 2. 确认 dwd.dim_member.birthday 列存在
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'dwd'
-- AND table_name = 'dim_member'
-- AND column_name = 'birthday';
-- 3. 确认列注释已设置
-- SELECT col_description(
-- (SELECT oid FROM pg_class WHERE relname = 'dim_member' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'dwd')),
-- (SELECT ordinal_position FROM information_schema.columns WHERE table_schema = 'dwd' AND table_name = 'dim_member' AND column_name = 'birthday')
-- );

View File

@@ -0,0 +1,77 @@
-- 迁移删除助教废除Abolish独立链路的数据库对象
-- 日期2026-02-22
-- 关联:助教废除全链路清理(需求 5.15.5
-- 原因:上游"助教废除记录"API 的独立数据链路已废弃。
-- 废除表 dwd_assistant_trash_event 无法与服务记录表做 1:1 关联(缺少外键),
-- DWS 层已改用 dwd_assistant_service_log_ex.is_trash 字段直接判断废除,
-- 整条 ODS → DWD 独立链路不再有消费者,属于死数据。
-- 影响:删除 3 张表 + 1 个索引,不影响 dwd_assistant_service_log_ex 中的
-- is_trash / trash_reason / trash_applicant_* 字段(保留)
-- 幂等:所有语句使用 IF EXISTS可重复执行
BEGIN;
-- ============================================================
-- 1. 删除 ODS 层索引
-- ============================================================
-- 该索引服务于 assistant_cancellation_records 的最新版本查询,表删除后索引无意义
DROP INDEX IF EXISTS ods.idx_ods_assistant_cancellation_records_latest;
-- ============================================================
-- 2. 删除 DWD 层废除表(先删扩展表,再删主表)
-- ============================================================
-- dwd_assistant_trash_event_ex废除事件扩展表无消费者
DROP TABLE IF EXISTS dwd.dwd_assistant_trash_event_ex;
-- dwd_assistant_trash_event废除事件主表无消费者
DROP TABLE IF EXISTS dwd.dwd_assistant_trash_event;
-- ============================================================
-- 3. 删除 ODS 层原始表
-- ============================================================
-- assistant_cancellation_records上游废除 API 原始数据(仅 78 条),不再抓取
DROP TABLE IF EXISTS ods.assistant_cancellation_records;
-- ============================================================
-- 4. 清理 meta 层任务注册(如存在)
-- ============================================================
-- 移除 ODS_ASSISTANT_ABOLISH 的调度注册,防止调度器尝试执行已删除的任务
DELETE FROM meta.etl_task WHERE task_code = 'ODS_ASSISTANT_ABOLISH';
COMMIT;
-- ============================================================
-- 回滚策略(需手动执行,不在事务内)
-- ============================================================
-- 1. 从 db/etl_feiqiu/schemas/ods.sql 中恢复 assistant_cancellation_records 的 CREATE TABLE
-- 2. 从 db/etl_feiqiu/schemas/dwd.sql 中恢复 dwd_assistant_trash_event / _ex 的 CREATE TABLE
-- 3. 重建索引:
-- CREATE INDEX idx_ods_assistant_cancellation_records_latest
-- ON ods.assistant_cancellation_records (id, fetched_at DESC);
-- 4. 重新注册任务:
-- INSERT INTO meta.etl_task (task_code, store_id, enabled)
-- VALUES ('ODS_ASSISTANT_ABOLISH', <store_id>, TRUE);
-- 5. 数据不可恢复ODS 仅 78 条,可从上游 API 重新抓取)
-- ============================================================
-- 验证 SQL
-- ============================================================
-- 1. 确认 3 张表已不存在
-- SELECT tablename FROM pg_tables
-- WHERE schemaname IN ('ods', 'dwd')
-- AND tablename IN (
-- 'assistant_cancellation_records',
-- 'dwd_assistant_trash_event',
-- 'dwd_assistant_trash_event_ex'
-- );
-- 预期0 行
-- 2. 确认索引已不存在
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'ods'
-- AND indexname = 'idx_ods_assistant_cancellation_records_latest';
-- 预期0 行
-- 3. 确认 meta.etl_task 中无 ODS_ASSISTANT_ABOLISH 注册
-- SELECT * FROM meta.etl_task WHERE task_code = 'ODS_ASSISTANT_ABOLISH';
-- 预期0 行

View File

@@ -0,0 +1,41 @@
-- 迁移:修复 DWD 层 BC 哨兵日期0001-12-31 23:59:43 BC→ NULL
-- 日期2026-02-22
-- 关联BUG 12 — 上游 API 返回 0001-01-01T00:00:00 哨兵值,
-- ODS timestamp 转 DWD timestamptz 时在 Asia/Shanghai 时区下变为 BC 日期,
-- psycopg2 无法解析 BC 日期导致 ValueError。
-- 影响6 个 DWD 表/列,共约 69,668 行
-- 幂等可重复执行WHERE 条件仅匹配 year < 1 的行
BEGIN;
-- 1. dwd.dim_assistant_ex.birth_date约 1,107 行)
UPDATE dwd.dim_assistant_ex
SET birth_date = NULL
WHERE EXTRACT(year FROM birth_date) < 1;
-- 2. dwd.dim_member_card_account_ex.disable_start_time约 18,172 行)
UPDATE dwd.dim_member_card_account_ex
SET disable_start_time = NULL
WHERE EXTRACT(year FROM disable_start_time) < 1;
-- 3. dwd.dim_member_card_account_ex.disable_end_time约 18,172 行)
UPDATE dwd.dim_member_card_account_ex
SET disable_end_time = NULL
WHERE EXTRACT(year FROM disable_end_time) < 1;
-- 4. dwd.dwd_assistant_service_log_ex.composite_grade_time约 5,297 行)
UPDATE dwd.dwd_assistant_service_log_ex
SET composite_grade_time = NULL
WHERE EXTRACT(year FROM composite_grade_time) < 1;
-- 5. dwd.dwd_recharge_order_ex.revoke_time约 485 行)
UPDATE dwd.dwd_recharge_order_ex
SET revoke_time = NULL
WHERE EXTRACT(year FROM revoke_time) < 1;
-- 6. dwd.dwd_settlement_head_ex.revoke_time约 26,435 行)
UPDATE dwd.dwd_settlement_head_ex
SET revoke_time = NULL
WHERE EXTRACT(year FROM revoke_time) < 1;
COMMIT;

View File

@@ -0,0 +1,144 @@
-- =============================================================================
-- 关系指数与 ML 人工台账迁移脚本
-- 版本: 2026-02-08
-- AI_CHANGELOG [2026-02-13] 移除 INSERT 中的 source_mode 参数ML 仅用人工台账)
-- 说明:
-- 1) 新增关系指数结果表 dws_member_assistant_relation_index
-- 2) 新增 ML 人工台账宽表/窄表
-- 3) 补充 RS/OS/MS/ML 参数并下线 INTIMACY
-- =============================================================================
BEGIN;
-- -----------------------------------------------------------------------------
-- 1) 关系指数结果表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_member_assistant_relation_index (
relation_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
session_count INTEGER NOT NULL DEFAULT 0,
total_duration_minutes INTEGER NOT NULL DEFAULT 0,
basic_session_count INTEGER NOT NULL DEFAULT 0,
incentive_session_count INTEGER NOT NULL DEFAULT 0,
days_since_last_session INTEGER,
rs_f NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_d NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_r NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
rs_display NUMERIC(4,2) NOT NULL DEFAULT 0,
os_share NUMERIC(10,6) NOT NULL DEFAULT 0,
os_label VARCHAR(20) NOT NULL DEFAULT 'POOL',
os_rank INTEGER,
ms_f_short NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_f_long NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
ms_display NUMERIC(4,2) NOT NULL DEFAULT 0,
ml_order_count INTEGER NOT NULL DEFAULT 0,
ml_allocated_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
ml_raw NUMERIC(14,6) NOT NULL DEFAULT 0,
ml_display NUMERIC(4,2) NOT NULL DEFAULT 0,
calc_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_member_assistant_relation_index UNIQUE (site_id, member_id, assistant_id)
);
CREATE INDEX IF NOT EXISTS idx_dws_relation_member
ON billiards_dws.dws_member_assistant_relation_index (site_id, member_id, os_share DESC);
CREATE INDEX IF NOT EXISTS idx_dws_relation_assistant
ON billiards_dws.dws_member_assistant_relation_index (site_id, assistant_id, rs_display DESC);
CREATE INDEX IF NOT EXISTS idx_dws_relation_calc_time
ON billiards_dws.dws_member_assistant_relation_index (calc_time);
-- -----------------------------------------------------------------------------
-- 2) ML 人工台账宽表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_ml_manual_order_source (
source_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
biz_date DATE NOT NULL,
external_id VARCHAR(128) NOT NULL,
member_id BIGINT NOT NULL DEFAULT 0,
pay_time TIMESTAMPTZ NOT NULL,
order_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
assistant_id_1 BIGINT,
assistant_name_1 VARCHAR(128),
assistant_id_2 BIGINT,
assistant_name_2 VARCHAR(128),
assistant_id_3 BIGINT,
assistant_name_3 VARCHAR(128),
assistant_id_4 BIGINT,
assistant_name_4 VARCHAR(128),
assistant_id_5 BIGINT,
assistant_name_5 VARCHAR(128),
import_batch_no VARCHAR(64) NOT NULL,
import_file_name VARCHAR(255) NOT NULL,
import_scope_key VARCHAR(128) NOT NULL,
import_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
import_user VARCHAR(64),
row_no INTEGER NOT NULL,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_ml_manual_order_source UNIQUE (site_id, external_id, import_scope_key, row_no)
);
CREATE INDEX IF NOT EXISTS idx_dws_ml_source_scope
ON billiards_dws.dws_ml_manual_order_source (site_id, biz_date);
CREATE INDEX IF NOT EXISTS idx_dws_ml_source_external
ON billiards_dws.dws_ml_manual_order_source (site_id, external_id);
-- -----------------------------------------------------------------------------
-- 3) ML 人工台账窄表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS billiards_dws.dws_ml_manual_order_alloc (
alloc_id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
biz_date DATE NOT NULL,
external_id VARCHAR(128) NOT NULL,
member_id BIGINT NOT NULL DEFAULT 0,
pay_time TIMESTAMPTZ NOT NULL,
order_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
assistant_id BIGINT NOT NULL,
assistant_name VARCHAR(128),
share_ratio NUMERIC(14,8) NOT NULL DEFAULT 0,
allocated_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
import_scope_key VARCHAR(128) NOT NULL,
import_batch_no VARCHAR(64) NOT NULL,
import_file_name VARCHAR(255) NOT NULL,
import_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
import_user VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_dws_ml_manual_order_alloc UNIQUE (site_id, external_id, assistant_id)
);
CREATE INDEX IF NOT EXISTS idx_dws_ml_alloc_scope
ON billiards_dws.dws_ml_manual_order_alloc (site_id, biz_date);
CREATE INDEX IF NOT EXISTS idx_dws_ml_alloc_member_assistant
ON billiards_dws.dws_ml_manual_order_alloc (site_id, member_id, assistant_id);
-- -----------------------------------------------------------------------------
-- 4) 参数切换
-- -----------------------------------------------------------------------------
UPDATE billiards_dws.cfg_index_parameters
SET effective_to = DATE '2025-12-31',
updated_at = NOW()
WHERE index_type = 'INTIMACY'
AND (effective_to IS NULL OR effective_to > DATE '2025-12-31');
INSERT INTO billiards_dws.cfg_index_parameters
(index_type, param_name, param_value, description, effective_from)
VALUES
('OS', 'ownership_gap_threshold', 0.150000, '主责与次席份额差阈值', DATE '2026-01-01')
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
param_value = EXCLUDED.param_value,
description = EXCLUDED.description,
updated_at = NOW();
COMMIT;

View File

@@ -0,0 +1,14 @@
-- ============================================================
-- ODS 表与 API JSON 字段对齐迁移
-- 自动生成于 2026-02-13
-- 基于: docs/api-reference/ 文档 vs billiards_ods 实际表结构
-- 比对逻辑: camelCase → snake_case 归一化后再比较
-- ============================================================
--
-- 结论: 22 张 ODS 表全部与 API JSON 字段对齐,无需任何 ALTER 操作。
--
-- stock_goods_category_tree 的 goodsCategoryList/total 为响应包装层字段,
-- ODS 表已正确展开存储数组内的记录级字段id, category_name, pid 等),
-- 不需要将包装层字段作为列添加。
--
-- 无需执行此文件。

View File

@@ -0,0 +1,72 @@
-- =============================================================================
-- 迁移移除旧版指数RECALL / INTIMACY及 ML last-touch 备用参数
-- 日期: 2026-02-13
-- AI_CHANGELOG [2026-02-13] 新建迁移DROP recall/intimacy 表DELETE 旧版参数/分位点/调度任务
-- 原因: 旧版 RecallIndexTask / IntimacyIndexTask 已被 WBI+NCI / RelationIndexTask 替代
-- ML 仅使用人工台账,不再需要 last-touch 备用路径
-- =============================================================================
BEGIN;
-- 1. 删除旧版指数参数
DELETE FROM billiards_dws.cfg_index_parameters
WHERE index_type IN ('RECALL', 'INTIMACY');
-- 2. 删除 ML 已废弃参数
DELETE FROM billiards_dws.cfg_index_parameters
WHERE index_type = 'ML'
AND param_name IN ('source_mode', 'recharge_attribute_hours');
-- 3. 删除旧版指数分位点历史
DELETE FROM billiards_dws.cfg_index_percentile_history
WHERE index_type IN ('RECALL', 'INTIMACY');
-- 4. 删除调度器中的旧版任务
DELETE FROM etl_admin.etl_task
WHERE task_code IN ('DWS_RECALL_INDEX', 'DWS_INTIMACY_INDEX');
-- 5. 删除旧版 recall 表(数据已由 WBI+NCI 替代)
DROP TABLE IF EXISTS billiards_dws.dws_member_recall_index CASCADE;
-- 6. 删除旧版 intimacy 表(数据已由 RelationIndexTask 替代)
DROP TABLE IF EXISTS billiards_dws.dws_member_assistant_intimacy CASCADE;
COMMIT;
-- =============================================================================
-- 验证
-- =============================================================================
DO $
DECLARE
recall_params INTEGER;
intimacy_params INTEGER;
ml_legacy INTEGER;
recall_table BOOLEAN;
intimacy_table BOOLEAN;
BEGIN
SELECT COUNT(*) INTO recall_params
FROM billiards_dws.cfg_index_parameters WHERE index_type = 'RECALL';
SELECT COUNT(*) INTO intimacy_params
FROM billiards_dws.cfg_index_parameters WHERE index_type = 'INTIMACY';
SELECT COUNT(*) INTO ml_legacy
FROM billiards_dws.cfg_index_parameters
WHERE index_type = 'ML' AND param_name IN ('source_mode', 'recharge_attribute_hours');
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'billiards_dws' AND table_name = 'dws_member_recall_index'
) INTO recall_table;
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'billiards_dws' AND table_name = 'dws_member_assistant_intimacy'
) INTO intimacy_table;
RAISE NOTICE 'RECALL 参数残留: % (应为 0)', recall_params;
RAISE NOTICE 'INTIMACY 参数残留: % (应为 0)', intimacy_params;
RAISE NOTICE 'ML 废弃参数残留: % (应为 0)', ml_legacy;
RAISE NOTICE 'recall 表存在: % (应为 false)', recall_table;
RAISE NOTICE 'intimacy 表存在: % (应为 false)', intimacy_table;
END $;

View File

@@ -0,0 +1,12 @@
-- 迁移:删除 DWD 层 dwd_settlement_head_ex 的 settle_list 列
-- 原因settle_list 存储结算明细 JSON与 ODS 层 payload 中的 settleList 完全重复
-- ODS 层 settlelist 列已在 20260214_drop_ods_settlelist.sql 中删除
-- DWD 层该列同样冗余,结算明细可随时从 ODS payload 提取
-- 回滚ALTER TABLE billiards_dwd.dwd_settlement_head_ex ADD COLUMN settle_list JSONB;
-- UPDATE billiards_dwd.dwd_settlement_head_ex e
-- SET settle_list = o.payload->'settleList'
-- FROM billiards_ods.settlement_records o
-- WHERE e.order_settle_id = o.id;
-- Prompt: P20260214-040000
ALTER TABLE billiards_dwd.dwd_settlement_head_ex DROP COLUMN IF EXISTS settle_list;

View File

@@ -0,0 +1,23 @@
-- 迁移:删除 ODS 层两个冗余列
-- 日期2026-02-14
-- Prompt-IDP20260214-070000
-- 原因option_namestore_goods_sales_records和 able_site_transfermember_stored_value_cards
-- 在 API JSON 响应中不存在ODS 中全部为 NULL0 条非空数据),属于冗余列
-- 回滚ALTER TABLE billiards_ods.store_goods_sales_records ADD COLUMN option_name TEXT;
-- ALTER TABLE billiards_ods.member_stored_value_cards ADD COLUMN able_site_transfer INTEGER;
-- CHANGE: intent=删除 API 中不存在的冗余 ODS 列; assumptions=两列全 NULL 无数据丢失风险; Prompt=P20260214-070000
BEGIN;
ALTER TABLE billiards_ods.store_goods_sales_records DROP COLUMN IF EXISTS option_name;
ALTER TABLE billiards_ods.member_stored_value_cards DROP COLUMN IF EXISTS able_site_transfer;
COMMIT;
-- AI_CHANGELOG:
-- - 日期: 2026-02-14
-- - Prompt: P20260214-070000 — ODS 清理与文档标注5 项任务)
-- - 直接原因: option_name 和 able_site_transfer 在 API JSON 中不存在ODS 全 NULL需删除冗余列
-- - 变更摘要: 新建迁移脚本DROP COLUMN 两张表各一列
-- - 风险与验证: 已执行成功验证information_schema 查询确认列不存在

View File

@@ -0,0 +1,34 @@
-- 迁移:删除 ODS 层 settlement_records / recharge_settlements 的 settlelist jsonb 列
-- 原因settlelist 与 payload 列数据重复payload 存储完整 API 响应 JSON已包含 settleList 对象)
-- 日期2026-02-14
-- Prompt-ID: P20260214-023000
--
-- 前置条件:确认 DWD 加载逻辑已修改为从 payload 提取 settleList而非直接读 settlelist 列)
-- 回滚:见文件末尾 ROLLBACK 部分
-- CHANGE: intent=删除冗余 settlelist 列,减少存储浪费
-- assumptions=payload 列已包含完整 settleList 数据DWD 加载已改为从 payload 提取
-- edge_cases=历史数据中 payload 为 NULL 的行将丢失 settleList 信息
-- prompt=P20260214-023000
-- 1) settlement_records
ALTER TABLE billiards_ods.settlement_records DROP COLUMN IF EXISTS settlelist;
-- 2) recharge_settlements
ALTER TABLE billiards_ods.recharge_settlements DROP COLUMN IF EXISTS settlelist;
-- 验证 SQL
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'billiards_ods'
-- AND table_name IN ('settlement_records', 'recharge_settlements')
-- AND column_name = 'settlelist';
-- 预期结果0 行
-- ============================================================
-- ROLLBACK回滚
-- ============================================================
-- ALTER TABLE billiards_ods.settlement_records ADD COLUMN settlelist jsonb;
-- ALTER TABLE billiards_ods.recharge_settlements ADD COLUMN settlelist jsonb;
-- 注意:回滚后列数据为 NULL需从 payload 中重新提取:
-- UPDATE billiards_ods.settlement_records SET settlelist = payload->'settleList' WHERE payload IS NOT NULL;
-- UPDATE billiards_ods.recharge_settlements SET settlelist = payload->'settleList' WHERE payload IS NOT NULL;

View File

@@ -0,0 +1,240 @@
-- =============================================================================
-- app schema DDL — 面向外部访问的视图/函数 + RLS 策略
-- 说明:以视图封装 DWS/Core 层数据,所有视图启用 RLS以 site_id 过滤
-- 不存储实际数据,仅做访问层
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS app;
SET search_path TO app;
-- -----------------------------------------------------------------------------
-- 应用角色(供 FDW 和外部应用使用)
-- -----------------------------------------------------------------------------
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_reader') THEN
CREATE ROLE app_reader;
END IF;
END
$$;
GRANT USAGE ON SCHEMA app TO app_reader;
GRANT USAGE ON SCHEMA core TO app_reader;
GRANT USAGE ON SCHEMA dws TO app_reader;
-- =============================================================================
-- 第一部分:基于 Core 层的视图
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. v_site — 门店视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_site AS
SELECT
s.site_id,
s.tenant_id,
s.shop_name,
s.site_label,
s.shop_status
FROM core.dim_site s;
COMMENT ON VIEW app.v_site IS '门店视图:封装 core.dim_site供外部应用访问。';
-- -----------------------------------------------------------------------------
-- 2. v_member — 会员视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_member AS
SELECT
m.member_id,
m.system_member_id,
m.tenant_id,
m.register_site_id AS site_id,
m.mobile,
m.nickname,
m.member_card_grade_name,
m.status
FROM core.dim_member m;
COMMENT ON VIEW app.v_member IS '会员视图:封装 core.dim_member以 register_site_id 作为 site_id。';
-- -----------------------------------------------------------------------------
-- 3. v_assistant — 助教视图
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_assistant AS
SELECT
a.assistant_id,
a.tenant_id,
a.site_id,
a.real_name,
a.nickname,
a.mobile,
a.level,
a.assistant_status,
a.leave_status
FROM core.dim_assistant a;
COMMENT ON VIEW app.v_assistant IS '助教视图:封装 core.dim_assistant。';
-- =============================================================================
-- 第二部分:基于 DWS 层的汇总视图
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 4. v_assistant_daily — 助教日明细视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_assistant_daily_detail 实际表结构
CREATE OR REPLACE VIEW app.v_assistant_daily AS
SELECT
d.id,
d.site_id,
d.tenant_id,
d.assistant_id,
d.assistant_nickname,
d.stat_date,
d.total_service_count,
d.total_hours,
d.base_hours,
d.bonus_hours,
d.room_hours,
d.total_ledger_amount,
d.unique_customers,
d.unique_tables,
d.created_at
FROM dws.dws_assistant_daily_detail d;
COMMENT ON VIEW app.v_assistant_daily IS '助教日明细视图:封装 dws.dws_assistant_daily_detail。';
-- -----------------------------------------------------------------------------
-- 5. v_finance_daily — 财务日报视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_finance_daily_summary 实际表结构
CREATE OR REPLACE VIEW app.v_finance_daily AS
SELECT
f.id,
f.site_id,
f.tenant_id,
f.stat_date,
f.gross_amount,
f.table_fee_amount,
f.goods_amount,
f.assistant_pd_amount,
f.assistant_cx_amount,
f.discount_total,
f.confirmed_income,
f.cash_inflow_total,
f.recharge_count,
f.recharge_total,
f.order_count,
f.member_order_count,
f.guest_order_count,
f.avg_order_amount,
f.created_at
FROM dws.dws_finance_daily_summary f;
COMMENT ON VIEW app.v_finance_daily IS '财务日报视图:封装 dws.dws_finance_daily_summary。';
-- -----------------------------------------------------------------------------
-- 6. v_member_consumption — 会员消费汇总视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_member_consumption_summary 实际表结构
CREATE OR REPLACE VIEW app.v_member_consumption AS
SELECT
mc.id,
mc.site_id,
mc.tenant_id,
mc.member_id,
mc.stat_date,
mc.member_nickname,
mc.card_grade_name,
mc.total_visit_count,
mc.total_consume_amount,
mc.total_recharge_amount,
mc.last_consume_date,
mc.first_consume_date,
mc.days_since_last,
mc.customer_tier,
mc.created_at
FROM dws.dws_member_consumption_summary mc;
COMMENT ON VIEW app.v_member_consumption IS '会员消费汇总视图:封装 dws.dws_member_consumption_summary。';
-- -----------------------------------------------------------------------------
-- 7. v_order_summary — 订单汇总视图
-- -----------------------------------------------------------------------------
-- CHANGE 2026-02-15 | 修复视图列名,对齐 dws.dws_order_summary 实际表结构
CREATE OR REPLACE VIEW app.v_order_summary AS
SELECT
os.site_id,
os.order_settle_id,
os.order_trade_no,
os.order_date,
os.tenant_id,
os.member_id,
os.member_flag,
os.order_original_amount,
os.order_final_amount,
os.total_paid_amount,
os.refund_amount,
os.net_income,
os.created_at
FROM dws.dws_order_summary os;
COMMENT ON VIEW app.v_order_summary IS '订单汇总视图:封装 dws.dws_order_summary。';
-- =============================================================================
-- 第三部分RLS 策略
-- 说明:所有视图基于 site_id 隔离,通过会话变量 app.current_site_id 过滤
-- 使用方式SET app.current_site_id = '2790685415443269';
-- =============================================================================
-- 对视图底层表启用 RLS视图本身不支持 RLS需在底层表上设置
-- 由于 app schema 的视图引用 core/dws 表RLS 需在源表上配置
-- core 层 RLS
ALTER TABLE core.dim_site ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_member ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_assistant ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.dim_table ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.fact_settlement ENABLE ROW LEVEL SECURITY;
ALTER TABLE core.fact_payment ENABLE ROW LEVEL SECURITY;
-- core 层策略
CREATE POLICY site_isolation_dim_site ON core.dim_site
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_member ON core.dim_member
FOR SELECT TO app_reader
USING (register_site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_assistant ON core.dim_assistant
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_dim_table ON core.dim_table
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_fact_settlement ON core.fact_settlement
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
CREATE POLICY site_isolation_fact_payment ON core.fact_payment
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
-- =============================================================================
-- 第四部分:授权
-- =============================================================================
-- 授予 app_reader 对 core 表的 SELECT 权限
GRANT SELECT ON ALL TABLES IN SCHEMA core TO app_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_reader;
-- 授予对 dws 表的 SELECT 权限(视图需要)
GRANT SELECT ON ALL TABLES IN SCHEMA dws TO app_reader;
-- 设置默认权限(未来新建的表自动授权)
ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT SELECT ON TABLES TO app_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA dws GRANT SELECT ON TABLES TO app_reader;

View File

@@ -0,0 +1,161 @@
-- =============================================================================
-- core schema DDL — 统一维度/事实最小字段集层
-- 说明:从 DWD 维度表和事实表提取跨系统共享的核心字段
-- 仅包含 ID、名称、状态、site_id 等最小字段集
-- 第一版保持精简,后续按需扩展
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS core;
SET search_path TO core;
-- -----------------------------------------------------------------------------
-- 1. dim_site — 门店维度(核心字段)
-- 来源dwd.dim_site14 字段 → 6 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_site (
site_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
shop_name TEXT NOT NULL,
site_label TEXT,
shop_status INTEGER,
site_id_alias BIGINT GENERATED ALWAYS AS (site_id) STORED
);
COMMENT ON TABLE core.dim_site IS '门店维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_site.site_id IS '门店 ID主键';
COMMENT ON COLUMN core.dim_site.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_site.shop_name IS '门店名称。';
COMMENT ON COLUMN core.dim_site.site_label IS '门店标签(如 A/B 店)。';
COMMENT ON COLUMN core.dim_site.shop_status IS '门店状态枚举。';
-- site_id_alias 用于 RLS 策略中统一使用 site_id 过滤
-- -----------------------------------------------------------------------------
-- 2. dim_member — 会员维度(核心字段)
-- 来源dwd.dim_member12 字段 → 8 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_member (
member_id BIGINT PRIMARY KEY,
system_member_id BIGINT,
tenant_id BIGINT NOT NULL,
register_site_id BIGINT NOT NULL,
mobile TEXT,
nickname TEXT,
member_card_grade_name TEXT,
status INTEGER
);
COMMENT ON TABLE core.dim_member IS '会员维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_member.member_id IS '会员 ID主键';
COMMENT ON COLUMN core.dim_member.system_member_id IS '系统级会员 ID跨门店统一';
COMMENT ON COLUMN core.dim_member.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_member.register_site_id IS '注册门店 ID等同 site_id';
COMMENT ON COLUMN core.dim_member.mobile IS '手机号。';
COMMENT ON COLUMN core.dim_member.nickname IS '昵称/姓名。';
COMMENT ON COLUMN core.dim_member.member_card_grade_name IS '会员卡等级名称。';
COMMENT ON COLUMN core.dim_member.status IS '会员状态。';
-- -----------------------------------------------------------------------------
-- 3. dim_assistant — 助教维度(核心字段)
-- 来源dwd.dim_assistant16 字段 → 9 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_assistant (
assistant_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
site_id BIGINT NOT NULL,
real_name TEXT NOT NULL,
nickname TEXT,
mobile TEXT,
level INTEGER,
assistant_status INTEGER,
leave_status INTEGER
);
COMMENT ON TABLE core.dim_assistant IS '助教维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_assistant.assistant_id IS '助教 ID主键';
COMMENT ON COLUMN core.dim_assistant.tenant_id IS '租户/品牌 ID。';
COMMENT ON COLUMN core.dim_assistant.site_id IS '门店 ID。';
COMMENT ON COLUMN core.dim_assistant.real_name IS '真实姓名。';
COMMENT ON COLUMN core.dim_assistant.nickname IS '昵称。';
COMMENT ON COLUMN core.dim_assistant.mobile IS '手机号。';
COMMENT ON COLUMN core.dim_assistant.level IS '助教等级。';
COMMENT ON COLUMN core.dim_assistant.assistant_status IS '助教状态。';
COMMENT ON COLUMN core.dim_assistant.leave_status IS '离职状态。';
-- -----------------------------------------------------------------------------
-- 4. dim_table — 台桌维度(核心字段)
-- 来源dwd.dim_table8 字段 → 5 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_table (
table_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
table_name TEXT NOT NULL,
site_table_area_name TEXT,
table_price NUMERIC(18,2)
);
COMMENT ON TABLE core.dim_table IS '台桌维度核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.dim_table.table_id IS '台桌 ID主键';
COMMENT ON COLUMN core.dim_table.site_id IS '门店 ID。';
COMMENT ON COLUMN core.dim_table.table_name IS '台桌名称。';
COMMENT ON COLUMN core.dim_table.site_table_area_name IS '区域名称。';
COMMENT ON COLUMN core.dim_table.table_price IS '台桌单价。';
-- -----------------------------------------------------------------------------
-- 5. dim_goods_category — 商品分类维度(核心字段)
-- 来源dwd.dim_goods_category10 字段 → 5 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dim_goods_category (
category_id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
category_name TEXT NOT NULL,
parent_id BIGINT,
level INTEGER
);
COMMENT ON TABLE core.dim_goods_category IS '商品分类维度核心表。';
-- -----------------------------------------------------------------------------
-- 6. fact_settlement — 结算事实(核心字段)
-- 来源dwd.dwd_settlement_head约 30 字段 → 12 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS fact_settlement (
order_settle_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
order_trade_no BIGINT,
member_id BIGINT,
total_amount NUMERIC(18,2),
actual_amount NUMERIC(18,2),
discount_amount NUMERIC(18,2),
pay_status INTEGER,
settle_time TIMESTAMPTZ,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE core.fact_settlement IS '结算事实核心表:仅保留跨系统共享的最小字段集。';
COMMENT ON COLUMN core.fact_settlement.order_settle_id IS '结账单 ID主键';
COMMENT ON COLUMN core.fact_settlement.site_id IS '门店 ID。';
COMMENT ON COLUMN core.fact_settlement.tenant_id IS '租户 ID。';
COMMENT ON COLUMN core.fact_settlement.total_amount IS '应收总额。';
COMMENT ON COLUMN core.fact_settlement.actual_amount IS '实收金额。';
COMMENT ON COLUMN core.fact_settlement.discount_amount IS '优惠金额。';
-- -----------------------------------------------------------------------------
-- 7. fact_payment — 支付事实(核心字段)
-- 来源dwd.dwd_payment约 12 字段 → 7 字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS fact_payment (
payment_id BIGINT PRIMARY KEY,
site_id BIGINT NOT NULL,
order_settle_id BIGINT,
pay_type INTEGER,
pay_amount NUMERIC(18,2),
pay_time TIMESTAMPTZ,
status INTEGER
);
COMMENT ON TABLE core.fact_payment IS '支付事实核心表。';
-- -----------------------------------------------------------------------------
-- 索引
-- -----------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_core_member_site ON core.dim_member (register_site_id);
CREATE INDEX IF NOT EXISTS idx_core_assistant_site ON core.dim_assistant (site_id);
CREATE INDEX IF NOT EXISTS idx_core_table_site ON core.dim_table (site_id);
CREATE INDEX IF NOT EXISTS idx_core_settlement_site ON core.fact_settlement (site_id);
CREATE INDEX IF NOT EXISTS idx_core_settlement_time ON core.fact_settlement (settle_time);
CREATE INDEX IF NOT EXISTS idx_core_payment_site ON core.fact_payment (site_id);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
-- 文件说明meta 调度元数据 DDL独立文件便于初始化任务单独执行
-- 包含任务注册表、游标表、运行记录表;字段注释使用中文。
CREATE SCHEMA IF NOT EXISTS meta;
CREATE TABLE IF NOT EXISTS meta.etl_task (
task_id BIGSERIAL PRIMARY KEY,
task_code TEXT NOT NULL,
store_id BIGINT NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
cursor_field TEXT,
window_minutes_default INT DEFAULT 30,
overlap_seconds INT DEFAULT 600,
page_size INT DEFAULT 200,
retry_max INT DEFAULT 3,
params JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_code, store_id)
);
COMMENT ON TABLE meta.etl_task IS '任务注册表:调度依据的任务清单(与 task_registry 中的任务码对应)。';
COMMENT ON COLUMN meta.etl_task.task_code IS '任务编码,需与代码中的任务码一致。';
COMMENT ON COLUMN meta.etl_task.store_id IS '门店/租户粒度,区分多门店执行。';
COMMENT ON COLUMN meta.etl_task.enabled IS '是否启用此任务。';
COMMENT ON COLUMN meta.etl_task.cursor_field IS '增量游标字段名(可选)。';
COMMENT ON COLUMN meta.etl_task.window_minutes_default IS '默认时间窗口(分钟)。';
COMMENT ON COLUMN meta.etl_task.overlap_seconds IS '窗口重叠秒数,用于防止遗漏。';
COMMENT ON COLUMN meta.etl_task.page_size IS '默认分页大小。';
COMMENT ON COLUMN meta.etl_task.retry_max IS 'API重试次数上限。';
COMMENT ON COLUMN meta.etl_task.params IS '任务级自定义参数 JSON。';
COMMENT ON COLUMN meta.etl_task.created_at IS '创建时间。';
COMMENT ON COLUMN meta.etl_task.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS meta.etl_cursor (
cursor_id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES meta.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
last_start TIMESTAMPTZ,
last_end TIMESTAMPTZ,
last_id BIGINT,
last_run_id BIGINT,
extra JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_id, store_id)
);
COMMENT ON TABLE meta.etl_cursor IS '任务游标表:记录每个任务/门店的增量窗口及最后 run。';
COMMENT ON COLUMN meta.etl_cursor.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN meta.etl_cursor.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN meta.etl_cursor.last_start IS '上次窗口开始时间(含重叠偏移)。';
COMMENT ON COLUMN meta.etl_cursor.last_end IS '上次窗口结束时间。';
COMMENT ON COLUMN meta.etl_cursor.last_id IS '上次处理的最大主键/游标值(可选)。';
COMMENT ON COLUMN meta.etl_cursor.last_run_id IS '上次运行ID对应 etl_run.run_id。';
COMMENT ON COLUMN meta.etl_cursor.extra IS '附加游标信息 JSON。';
COMMENT ON COLUMN meta.etl_cursor.created_at IS '创建时间。';
COMMENT ON COLUMN meta.etl_cursor.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS meta.etl_run (
run_id BIGSERIAL PRIMARY KEY,
run_uuid TEXT NOT NULL,
task_id BIGINT NOT NULL REFERENCES meta.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMPTZ DEFAULT now(),
ended_at TIMESTAMPTZ,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
window_minutes INT,
overlap_seconds INT,
fetched_count INT DEFAULT 0,
loaded_count INT DEFAULT 0,
updated_count INT DEFAULT 0,
skipped_count INT DEFAULT 0,
error_count INT DEFAULT 0,
unknown_fields INT DEFAULT 0,
export_dir TEXT,
log_path TEXT,
request_params JSONB DEFAULT '{}'::jsonb,
manifest JSONB DEFAULT '{}'::jsonb,
error_message TEXT,
extra JSONB DEFAULT '{}'::jsonb
);
COMMENT ON TABLE meta.etl_run IS '运行记录表:记录每次任务执行的窗口、状态、计数与日志路径。';
COMMENT ON COLUMN meta.etl_run.run_uuid IS '本次调度的唯一标识。';
COMMENT ON COLUMN meta.etl_run.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN meta.etl_run.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN meta.etl_run.status IS '运行状态SUCC/FAIL/PARTIAL 等)。';
COMMENT ON COLUMN meta.etl_run.started_at IS '开始时间。';
COMMENT ON COLUMN meta.etl_run.ended_at IS '结束时间。';
COMMENT ON COLUMN meta.etl_run.window_start IS '本次窗口开始时间。';
COMMENT ON COLUMN meta.etl_run.window_end IS '本次窗口结束时间。';
COMMENT ON COLUMN meta.etl_run.window_minutes IS '窗口跨度(分钟)。';
COMMENT ON COLUMN meta.etl_run.overlap_seconds IS '窗口重叠秒数。';
COMMENT ON COLUMN meta.etl_run.fetched_count IS '抓取/读取的记录数。';
COMMENT ON COLUMN meta.etl_run.loaded_count IS '插入的记录数。';
COMMENT ON COLUMN meta.etl_run.updated_count IS '更新的记录数。';
COMMENT ON COLUMN meta.etl_run.skipped_count IS '跳过的记录数。';
COMMENT ON COLUMN meta.etl_run.error_count IS '错误记录数。';
COMMENT ON COLUMN meta.etl_run.unknown_fields IS '未知字段计数(清洗阶段)。';
COMMENT ON COLUMN meta.etl_run.export_dir IS '抓取/导出目录。';
COMMENT ON COLUMN meta.etl_run.log_path IS '日志路径。';
COMMENT ON COLUMN meta.etl_run.request_params IS '请求参数 JSON。';
COMMENT ON COLUMN meta.etl_run.manifest IS '运行产出清单/统计 JSON。';
COMMENT ON COLUMN meta.etl_run.error_message IS '错误信息(若失败)。';
COMMENT ON COLUMN meta.etl_run.extra IS '附加字段,保留扩展。';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
-- 文件说明etl_admin 调度元数据 DDL独立文件便于初始化任务单独执行
-- 包含任务注册表、游标表、运行记录表;字段注释使用中文。
CREATE SCHEMA IF NOT EXISTS etl_admin;
CREATE TABLE IF NOT EXISTS etl_admin.etl_task (
task_id BIGSERIAL PRIMARY KEY,
task_code TEXT NOT NULL,
store_id BIGINT NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
cursor_field TEXT,
window_minutes_default INT DEFAULT 30,
overlap_seconds INT DEFAULT 600,
page_size INT DEFAULT 200,
retry_max INT DEFAULT 3,
params JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_code, store_id)
);
COMMENT ON TABLE etl_admin.etl_task IS '任务注册表:调度依据的任务清单(与 task_registry 中的任务码对应)。';
COMMENT ON COLUMN etl_admin.etl_task.task_code IS '任务编码,需与代码中的任务码一致。';
COMMENT ON COLUMN etl_admin.etl_task.store_id IS '门店/租户粒度,区分多门店执行。';
COMMENT ON COLUMN etl_admin.etl_task.enabled IS '是否启用此任务。';
COMMENT ON COLUMN etl_admin.etl_task.cursor_field IS '增量游标字段名(可选)。';
COMMENT ON COLUMN etl_admin.etl_task.window_minutes_default IS '默认时间窗口(分钟)。';
COMMENT ON COLUMN etl_admin.etl_task.overlap_seconds IS '窗口重叠秒数,用于防止遗漏。';
COMMENT ON COLUMN etl_admin.etl_task.page_size IS '默认分页大小。';
COMMENT ON COLUMN etl_admin.etl_task.retry_max IS 'API重试次数上限。';
COMMENT ON COLUMN etl_admin.etl_task.params IS '任务级自定义参数 JSON。';
COMMENT ON COLUMN etl_admin.etl_task.created_at IS '创建时间。';
COMMENT ON COLUMN etl_admin.etl_task.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS etl_admin.etl_cursor (
cursor_id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES etl_admin.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
last_start TIMESTAMPTZ,
last_end TIMESTAMPTZ,
last_id BIGINT,
last_run_id BIGINT,
extra JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (task_id, store_id)
);
COMMENT ON TABLE etl_admin.etl_cursor IS '任务游标表:记录每个任务/门店的增量窗口及最后 run。';
COMMENT ON COLUMN etl_admin.etl_cursor.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN etl_admin.etl_cursor.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_start IS '上次窗口开始时间(含重叠偏移)。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_end IS '上次窗口结束时间。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_id IS '上次处理的最大主键/游标值(可选)。';
COMMENT ON COLUMN etl_admin.etl_cursor.last_run_id IS '上次运行ID对应 etl_run.run_id。';
COMMENT ON COLUMN etl_admin.etl_cursor.extra IS '附加游标信息 JSON。';
COMMENT ON COLUMN etl_admin.etl_cursor.created_at IS '创建时间。';
COMMENT ON COLUMN etl_admin.etl_cursor.updated_at IS '更新时间。';
CREATE TABLE IF NOT EXISTS etl_admin.etl_run (
run_id BIGSERIAL PRIMARY KEY,
run_uuid TEXT NOT NULL,
task_id BIGINT NOT NULL REFERENCES etl_admin.etl_task(task_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMPTZ DEFAULT now(),
ended_at TIMESTAMPTZ,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
window_minutes INT,
overlap_seconds INT,
fetched_count INT DEFAULT 0,
loaded_count INT DEFAULT 0,
updated_count INT DEFAULT 0,
skipped_count INT DEFAULT 0,
error_count INT DEFAULT 0,
unknown_fields INT DEFAULT 0,
export_dir TEXT,
log_path TEXT,
request_params JSONB DEFAULT '{}'::jsonb,
manifest JSONB DEFAULT '{}'::jsonb,
error_message TEXT,
extra JSONB DEFAULT '{}'::jsonb
);
COMMENT ON TABLE etl_admin.etl_run IS '运行记录表:记录每次任务执行的窗口、状态、计数与日志路径。';
COMMENT ON COLUMN etl_admin.etl_run.run_uuid IS '本次调度的唯一标识。';
COMMENT ON COLUMN etl_admin.etl_run.task_id IS '关联 etl_task.task_id。';
COMMENT ON COLUMN etl_admin.etl_run.store_id IS '门店/租户粒度。';
COMMENT ON COLUMN etl_admin.etl_run.status IS '运行状态SUCC/FAIL/PARTIAL 等)。';
COMMENT ON COLUMN etl_admin.etl_run.started_at IS '开始时间。';
COMMENT ON COLUMN etl_admin.etl_run.ended_at IS '结束时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_start IS '本次窗口开始时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_end IS '本次窗口结束时间。';
COMMENT ON COLUMN etl_admin.etl_run.window_minutes IS '窗口跨度(分钟)。';
COMMENT ON COLUMN etl_admin.etl_run.overlap_seconds IS '窗口重叠秒数。';
COMMENT ON COLUMN etl_admin.etl_run.fetched_count IS '抓取/读取的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.loaded_count IS '插入的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.updated_count IS '更新的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.skipped_count IS '跳过的记录数。';
COMMENT ON COLUMN etl_admin.etl_run.error_count IS '错误记录数。';
COMMENT ON COLUMN etl_admin.etl_run.unknown_fields IS '未知字段计数(清洗阶段)。';
COMMENT ON COLUMN etl_admin.etl_run.export_dir IS '抓取/导出目录。';
COMMENT ON COLUMN etl_admin.etl_run.log_path IS '日志路径。';
COMMENT ON COLUMN etl_admin.etl_run.request_params IS '请求参数 JSON。';
COMMENT ON COLUMN etl_admin.etl_run.manifest IS '运行产出清单/统计 JSON。';
COMMENT ON COLUMN etl_admin.etl_run.error_message IS '错误信息(若失败)。';
COMMENT ON COLUMN etl_admin.etl_run.extra IS '附加字段,保留扩展。';

View File

@@ -0,0 +1,173 @@
SET client_encoding TO "UTF8";
-- ============================================================================
-- 校验性能索引ODS / DWD
-- ----------------------------------------------------------------------------
-- 用途:
-- 1) 加速校验查询(主键查找、窗口扫描、当前版本扫描)。
-- 2) 保持数据语义不变(仅添加索引 + ANALYZE不改写业务数据
--
-- 注意事项:
-- 1) 本脚本具有幂等性(`CREATE INDEX IF NOT EXISTS`)。
-- 2) 如有严格的在线 DDL 要求,请手动使用 `CREATE INDEX CONCURRENTLY`
-- 在维护安全模式下执行(不可在事务块内运行)。
-- ============================================================================
DO $$
DECLARE
rec RECORD;
pk_cols TEXT[];
pk_cols_sql TEXT;
idx_name TEXT;
BEGIN
FOR rec IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'billiards_ods'
AND table_type = 'BASE TABLE'
LOOP
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_ods'
AND table_name = rec.table_name
AND column_name = 'fetched_at'
) THEN
idx_name := left(format('idx_%s_vfy_fetched_at', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_fetched_at'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_ods.%I (fetched_at)',
idx_name, rec.table_name
);
SELECT array_agg(kcu.column_name ORDER BY kcu.ordinal_position)
INTO pk_cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.table_schema = kcu.table_schema
AND tc.table_name = kcu.table_name
AND tc.constraint_name = kcu.constraint_name
WHERE tc.table_schema = 'billiards_ods'
AND tc.table_name = rec.table_name
AND tc.constraint_type = 'PRIMARY KEY';
IF pk_cols IS NOT NULL AND coalesce(array_length(pk_cols, 1), 0) <= 3 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_fetched_pk', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_fetched_pk'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_ods.%I (fetched_at, %s)',
idx_name, rec.table_name, pk_cols_sql
);
END IF;
END IF;
END LOOP;
END
$$;
DO $$
DECLARE
rec RECORD;
tcol TEXT;
pk_cols TEXT[];
pk_cols_sql TEXT;
idx_name TEXT;
time_candidates TEXT[] := ARRAY[
'pay_time',
'create_time',
'start_use_time',
'scd2_start_time',
'calc_time',
'order_date',
'fetched_at'
];
BEGIN
FOR rec IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'billiards_dwd'
AND table_type = 'BASE TABLE'
LOOP
SELECT array_agg(kcu.column_name ORDER BY kcu.ordinal_position)
INTO pk_cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.table_schema = kcu.table_schema
AND tc.table_name = kcu.table_name
AND tc.constraint_name = kcu.constraint_name
WHERE tc.table_schema = 'billiards_dwd'
AND tc.table_name = rec.table_name
AND tc.constraint_type = 'PRIMARY KEY';
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_dwd'
AND table_name = rec.table_name
AND column_name = 'scd2_is_current'
) AND pk_cols IS NOT NULL
AND coalesce(array_length(pk_cols, 1), 0) BETWEEN 1 AND 4 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_pk_current', rec.table_name), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_pk_current'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%s, scd2_is_current)',
idx_name, rec.table_name, pk_cols_sql
);
END IF;
FOREACH tcol IN ARRAY time_candidates
LOOP
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'billiards_dwd'
AND table_name = rec.table_name
AND column_name = tcol
) THEN
idx_name := left(format('idx_%s_vfy_%s', rec.table_name, tcol), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_' || tcol), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%I)',
idx_name, rec.table_name, tcol
);
IF pk_cols IS NOT NULL AND coalesce(array_length(pk_cols, 1), 0) <= 3 THEN
SELECT string_agg(format('%I', c), ', ')
INTO pk_cols_sql
FROM unnest(pk_cols) AS c;
idx_name := left(format('idx_%s_vfy_%s_pk', rec.table_name, tcol), 50)
|| '_' || substr(md5(rec.table_name || '_vfy_' || tcol || '_pk'), 1, 8);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON billiards_dwd.%I (%I, %s)',
idx_name, rec.table_name, tcol, pk_cols_sql
);
END IF;
END IF;
END LOOP;
END LOOP;
END
$$;
DO $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema IN ('billiards_ods', 'billiards_dwd')
AND table_type = 'BASE TABLE'
LOOP
EXECUTE format('ANALYZE %I.%I', rec.table_schema, rec.table_name);
END LOOP;
END
$$;

View File

@@ -0,0 +1,107 @@
-- =============================================================================
-- 从 LLZQ-test 迁移测试数据到新结构
-- 源数据库LLZQ-test旧测试库包含 billiards_ods/billiards_dwd/billiards_dws/etl_admin
-- 目标数据库test_etl_feiqiu新六层 schema+ test_zqyy_app业务应用库
--
-- 前置条件:
-- 1. 已执行 db/etl_feiqiu/scripts/create_test_db.sql 创建 test_etl_feiqiu
-- 2. 已执行 db/zqyy_app/scripts/create_test_db.sql 创建 test_zqyy_app
-- 3. 当前用户对 LLZQ-test 有 SELECT 权限,对目标库有 INSERT 权限
--
-- 执行方式:
-- psql -h <host> -U <user> -f migrate_test_data.sql
--
-- 注意:需要根据实际表映射关系调整,以下为参考模板
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 第一部分etl_admin → meta调度/游标/运行记录)
-- ---------------------------------------------------------------------------
-- 迁移运行记录
INSERT INTO test_etl_feiqiu.meta.etl_run_log
SELECT * FROM dblink(
'dbname=LLZQ-test',
'SELECT * FROM etl_admin.etl_run_log'
) AS t(
-- 根据 meta.sql 中 etl_run_log 的实际列定义填充
-- 示例run_id bigint, task_code text, started_at timestamptz, ...
);
-- 迁移游标状态
INSERT INTO test_etl_feiqiu.meta.etl_cursor_state
SELECT * FROM dblink(
'dbname=LLZQ-test',
'SELECT * FROM etl_admin.etl_cursor_state'
) AS t(
-- 根据 meta.sql 中 etl_cursor_state 的实际列定义填充
);
-- ---------------------------------------------------------------------------
-- 第二部分billiards_ods → odsODS 原始数据)
-- schema 名从 billiards_ods 改为 ods表结构不变
-- ---------------------------------------------------------------------------
-- 会员档案
INSERT INTO test_etl_feiqiu.ods.member_profiles
SELECT * FROM dblink(
'dbname=LLZQ-test',
'SELECT * FROM billiards_ods.member_profiles'
) AS t(
-- 根据 ods.sql 中 member_profiles 的实际列定义填充
);
-- 会员余额变动
INSERT INTO test_etl_feiqiu.ods.member_balance_changes
SELECT * FROM dblink(
'dbname=LLZQ-test',
'SELECT * FROM billiards_ods.member_balance_changes'
) AS t(
-- 根据 ods.sql 中 member_balance_changes 的实际列定义填充
);
-- ... 对 ods schema 中的每张表重复上述模式 ...
-- 完整表清单参见 db/etl_feiqiu/schemas/ods.sql 中的 CREATE TABLE 语句
-- ---------------------------------------------------------------------------
-- 第三部分billiards_dwd → dwdDWD 明细数据)
-- schema 名从 billiards_dwd 改为 dwd保留 main+EX 拆分
-- ---------------------------------------------------------------------------
-- ... 对 dwd schema 中的每张表执行类似迁移 ...
-- 完整表清单参见 db/etl_feiqiu/schemas/dwd.sql 中的 CREATE TABLE 语句
-- ---------------------------------------------------------------------------
-- 第四部分billiards_dws → dwsDWS 汇总数据)
-- schema 名从 billiards_dws 改为 dws表结构不变
-- ---------------------------------------------------------------------------
-- ... 对 dws schema 中的每张表执行类似迁移 ...
-- 完整表清单参见 db/etl_feiqiu/schemas/dws.sql 中的 CREATE TABLE 语句
-- ---------------------------------------------------------------------------
-- 第五部分core schema新增层无历史数据可迁移
-- core 层为新增的统一最小字段集层,需从 dwd 数据重新生成
-- ---------------------------------------------------------------------------
-- 如需填充 core 层测试数据,可从已迁移的 dwd 数据中提取:
-- INSERT INTO test_etl_feiqiu.core.dim_member (member_id, name, mobile, status, site_id)
-- SELECT member_id, name, mobile, status, site_id
-- FROM test_etl_feiqiu.dwd.dim_member_main;
-- ---------------------------------------------------------------------------
-- 第六部分app schema视图层无需迁移数据
-- app 层仅包含视图和 RLS 策略,数据来自底层 schema
-- ---------------------------------------------------------------------------
-- 无需数据迁移,视图会自动引用底层表数据
-- ---------------------------------------------------------------------------
-- 迁移完成后验证
-- ---------------------------------------------------------------------------
-- 验证各 schema 表行数是否与源库一致
-- SELECT schemaname, relname, n_live_tup
-- FROM pg_stat_user_tables
-- WHERE schemaname IN ('meta', 'ods', 'dwd', 'dws', 'core')
-- ORDER BY schemaname, relname;

View File

@@ -0,0 +1,107 @@
-- =============================================================================
-- 迁移脚本:创建 Web 管理后台所需的 4 张表
-- 数据库zqyy_app
-- 关联需求Requirements 1.1用户认证、4.1任务队列、5.1(调度任务)
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- admin_users 表:管理后台操作员账户
-- 每个 Operator 绑定一个门店site_id登录后 JWT 携带 site_id
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS admin_users (
id SERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(256) NOT NULL,
display_name VARCHAR(128),
site_id BIGINT NOT NULL, -- 绑定的门店 ID
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_admin_users_site
ON admin_users(site_id);
-- ---------------------------------------------------------------------------
-- task_queue 表ETL 任务执行队列
-- 状态流转pending → running → success / failed / cancelled
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS task_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id BIGINT NOT NULL, -- 门店隔离
config JSONB NOT NULL, -- 序列化的 TaskConfig
status VARCHAR(20) NOT NULL DEFAULT 'pending',
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
exit_code INTEGER,
error_message TEXT
);
-- 按状态查询(如获取所有 running 任务)
CREATE INDEX IF NOT EXISTS idx_task_queue_status
ON task_queue(status);
-- 按门店 + 位置查询待执行任务(部分索引,仅 pending 状态)
CREATE INDEX IF NOT EXISTS idx_task_queue_site_position
ON task_queue(site_id, position)
WHERE status = 'pending';
-- ---------------------------------------------------------------------------
-- task_execution_log 表:任务执行历史记录
-- 每次执行(无论来自队列还是直接触发)都记录一条日志
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS task_execution_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
queue_id UUID REFERENCES task_queue(id),
site_id BIGINT NOT NULL, -- 门店隔离
task_codes TEXT[] NOT NULL,
status VARCHAR(20) NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
finished_at TIMESTAMPTZ,
exit_code INTEGER,
duration_ms INTEGER,
command TEXT, -- 实际执行的 CLI 命令
output_log TEXT, -- stdout 完整日志
error_log TEXT, -- stderr 日志
summary JSONB, -- 执行摘要
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 按门店 + 开始时间倒序查询(执行历史列表)
CREATE INDEX IF NOT EXISTS idx_execution_log_site_started
ON task_execution_log(site_id, started_at DESC);
-- ---------------------------------------------------------------------------
-- scheduled_tasks 表:定时调度任务
-- 支持一次性 / 固定间隔 / 每日 / 每周 / Cron 五种调度类型
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS scheduled_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id BIGINT NOT NULL, -- 门店隔离
name VARCHAR(256) NOT NULL,
task_codes TEXT[] NOT NULL,
task_config JSONB NOT NULL, -- 序列化的 TaskConfig
schedule_config JSONB NOT NULL, -- 序列化的 ScheduleConfig
enabled BOOLEAN DEFAULT TRUE,
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
run_count INTEGER DEFAULT 0,
last_status VARCHAR(20),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 按门店查询调度任务
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_site
ON scheduled_tasks(site_id);
-- 查询到期的已启用调度任务(部分索引)
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_next_run
ON scheduled_tasks(next_run_at)
WHERE enabled = TRUE;
COMMIT;

View File

@@ -0,0 +1,81 @@
-- 迁移:创建 member_birthday_manual 表(助教手动补录会员生日)
-- 日期2026-02-22
-- 关联:需求 C2需求 5.1)— 助教手动补录会员生日
-- 目标库zqyy_app / test_zqyy_app
-- 影响:新建 member_birthday_manual 表 + 唯一约束 + 索引
-- 幂等CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS可重复执行
BEGIN;
-- ============================================================
-- 1. 创建 member_birthday_manual 表
-- ============================================================
CREATE TABLE IF NOT EXISTS member_birthday_manual (
id BIGSERIAL PRIMARY KEY,
member_id BIGINT NOT NULL,
birthday_value DATE NOT NULL,
recorded_by_assistant_id BIGINT,
recorded_by_name VARCHAR(50),
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
source VARCHAR(20) DEFAULT 'assistant',
site_id BIGINT NOT NULL,
CONSTRAINT uk_member_birthday_manual
UNIQUE (member_id, recorded_by_assistant_id)
);
COMMENT ON TABLE member_birthday_manual
IS '助教手动补录的会员生日信息';
-- ============================================================
-- 2. 创建索引
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_mbd_member
ON member_birthday_manual (member_id);
CREATE INDEX IF NOT EXISTS idx_mbd_site_id
ON member_birthday_manual (site_id);
COMMIT;
-- ============================================================
-- 回滚(需手动执行,不在事务内)
-- ============================================================
-- BEGIN;
-- DROP TABLE IF EXISTS member_birthday_manual CASCADE;
-- COMMIT;
-- ============================================================
-- 验证 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. 确认列结构完整8 列)
-- 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;
-- 预期id, member_id, birthday_value, recorded_by_assistant_id,
-- recorded_by_name, recorded_at, source, site_id

View File

@@ -0,0 +1,127 @@
-- =============================================================================
-- zqyy_app 数据库 Schema DDL
-- 业务应用数据库用户管理、RBAC 权限、任务管理、审批流程
-- 所有业务表包含 site_id 字段以支持多门店隔离Requirements 13.1
-- =============================================================================
-- ---------------------------------------------------------------------------
-- users 表:用户账户(微信 OpenID、手机号、昵称
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
wx_openid TEXT UNIQUE,
mobile TEXT,
nickname TEXT,
status INT DEFAULT 1, -- 1=启用, 0=禁用
site_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_users_site_id ON users (site_id);
CREATE INDEX IF NOT EXISTS idx_users_mobile ON users (mobile);
-- ---------------------------------------------------------------------------
-- roles 表:角色定义
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS roles (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT,
site_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_roles_site_id ON roles (site_id);
-- ---------------------------------------------------------------------------
-- permissions 表:权限定义(全局,不需要 site_id
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS permissions (
id SERIAL PRIMARY KEY,
resource TEXT NOT NULL, -- 资源标识,如 'order', 'member'
action TEXT NOT NULL, -- 操作标识,如 'read', 'write', 'delete'
description TEXT,
UNIQUE (resource, action)
);
-- ---------------------------------------------------------------------------
-- user_roles 关联表:用户-角色多对多
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS user_roles (
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
site_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
);
CREATE INDEX IF NOT EXISTS idx_user_roles_site_id ON user_roles (site_id);
-- ---------------------------------------------------------------------------
-- role_permissions 关联表:角色-权限多对多
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
permission_id INT REFERENCES permissions(id) ON DELETE CASCADE,
PRIMARY KEY (role_id, permission_id)
);
-- ---------------------------------------------------------------------------
-- tasks 表:任务管理(含审批流)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS tasks (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'pending', -- pending / approved / rejected / completed
assignee_id BIGINT REFERENCES users(id),
creator_id BIGINT REFERENCES users(id),
site_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_tasks_site_id ON tasks (site_id);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks (status);
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks (assignee_id);
-- ---------------------------------------------------------------------------
-- approvals 表:审批记录
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS approvals (
id BIGSERIAL PRIMARY KEY,
task_id BIGINT REFERENCES tasks(id) ON DELETE CASCADE,
approver_id BIGINT REFERENCES users(id),
status TEXT DEFAULT 'pending', -- pending / approved / rejected
comment TEXT,
site_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_approvals_site_id ON approvals (site_id);
CREATE INDEX IF NOT EXISTS idx_approvals_task_id ON approvals (task_id);
-- ---------------------------------------------------------------------------
-- member_birthday_manual 表:助教手动补录的会员生日信息
-- 关联迁移2026-02-22__C2_member_birthday_manual.sql
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS member_birthday_manual (
id BIGSERIAL PRIMARY KEY,
member_id BIGINT NOT NULL,
birthday_value DATE NOT NULL,
recorded_by_assistant_id BIGINT,
recorded_by_name VARCHAR(50),
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
source VARCHAR(20) DEFAULT 'assistant',
site_id BIGINT NOT NULL,
CONSTRAINT uk_member_birthday_manual
UNIQUE (member_id, recorded_by_assistant_id)
);
COMMENT ON TABLE member_birthday_manual
IS '助教手动补录的会员生日信息';
CREATE INDEX IF NOT EXISTS idx_mbd_member
ON member_birthday_manual (member_id);
CREATE INDEX IF NOT EXISTS idx_mbd_site_id
ON member_birthday_manual (site_id);