微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
-- 新增 dws.biz_date() 函数:将 timestamptz 按营业日分割点归属到对应日期
|
||||
-- 原因:全系统统计时间口径从自然日切换为营业日(默认 08:00 分割),
|
||||
-- 数据库层需要与 Python 侧 business_date() 等价的 SQL 函数,
|
||||
-- 供物化视图和直接查询使用。
|
||||
-- 影响:物化视图 mv_dws_finance_daily_summary_l1..l4、
|
||||
-- mv_dws_assistant_daily_detail_l1..l4 的时间过滤条件将依赖此函数
|
||||
-- 关联需求:Requirements 9.2, 9.4
|
||||
|
||||
CREATE OR REPLACE FUNCTION dws.biz_date(
|
||||
ts timestamptz,
|
||||
cutoff_hour int DEFAULT 8
|
||||
)
|
||||
RETURNS date
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
PARALLEL SAFE
|
||||
AS $$
|
||||
SELECT (ts - make_interval(hours => cutoff_hour))::date;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION dws.biz_date(timestamptz, int) IS
|
||||
'营业日归属函数:将时间戳减去 cutoff_hour 小时后取日期。'
|
||||
'默认 cutoff_hour=8,即 08:00 前的时间戳归属前一天。'
|
||||
'等价于 Python 侧 neozqyy_shared.datetime_utils.business_date()。';
|
||||
|
||||
-- 验证 SQL:
|
||||
-- SELECT dws.biz_date('2026-01-15 07:59:59+08'::timestamptz); -- 预期:2026-01-14
|
||||
-- SELECT dws.biz_date('2026-01-15 08:00:00+08'::timestamptz); -- 预期:2026-01-15
|
||||
-- SELECT dws.biz_date('2026-01-15 23:59:59+08'::timestamptz); -- 预期:2026-01-15
|
||||
-- SELECT dws.biz_date('2026-02-01 07:00:00+08'::timestamptz, 8); -- 预期:2026-01-31(月末边界)
|
||||
-- SELECT dws.biz_date(NOW()); -- 预期:当前营业日
|
||||
|
||||
-- 回滚:
|
||||
-- DROP FUNCTION IF EXISTS dws.biz_date(timestamptz, int);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 迁移:dws_assistant_order_contribution.site_id integer → bigint
|
||||
-- 原因:site_id 为飞球雪花 ID,值域超出 int32 上限
|
||||
-- 依赖:app.v_dws_assistant_order_contribution 视图需先 DROP 再重建
|
||||
-- 日期:2026-02-27
|
||||
-- 已通过 scripts/ops/_fix_all_int_site_ids.py 执行
|
||||
|
||||
-- 1. DROP 依赖视图
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
|
||||
|
||||
-- 2. ALTER 列类型
|
||||
ALTER TABLE dws.dws_assistant_order_contribution
|
||||
ALTER COLUMN site_id TYPE bigint;
|
||||
|
||||
-- 3. 重建视图(定义需与原视图一致)
|
||||
-- CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
-- SELECT * FROM dws.dws_assistant_order_contribution;
|
||||
-- 注意:实际视图定义已由脚本自动获取并重建
|
||||
|
||||
-- 回滚(如需):
|
||||
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
|
||||
-- ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN site_id TYPE integer;
|
||||
-- 然后重建视图
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 修复 dws_member_spending_power_index.site_id: integer → bigint
|
||||
-- 原因:site_id 值(如 2790685415443269)超出 int32 范围(max 2147483647),
|
||||
-- 导致 INSERT 时 NumericValueOutOfRange 错误
|
||||
-- 影响:DWS_SPENDING_POWER_INDEX 任务
|
||||
-- 已在 test_etl_feiqiu 执行:2026-02-27
|
||||
|
||||
ALTER TABLE dws.dws_member_spending_power_index
|
||||
ALTER COLUMN site_id TYPE bigint;
|
||||
|
||||
-- 回滚(仅在确认无大值数据时):
|
||||
-- ALTER TABLE dws.dws_member_spending_power_index ALTER COLUMN site_id TYPE integer;
|
||||
|
||||
-- 待处理:dws.dws_assistant_order_contribution.site_id 也是 integer,
|
||||
-- 但有视图 app.v_dws_assistant_order_contribution 依赖,需先 DROP VIEW 再改再重建。
|
||||
@@ -0,0 +1,138 @@
|
||||
-- 重建物化视图:时间过滤条件从自然日切换为营业日
|
||||
-- 原因:全系统统计口径从自然日切换为营业日(默认 08:00 分割),
|
||||
-- 物化视图的 WHERE 条件中 CURRENT_DATE 需替换为 dws.biz_date(NOW()),
|
||||
-- 使视图数据范围与 DWS 任务的营业日口径一致。
|
||||
-- 前置依赖:dws.biz_date() 函数(2026-02-27__add_biz_date_function.sql)
|
||||
-- 影响:8 个物化视图将按营业日口径过滤数据,刷新后生效
|
||||
-- 关联需求:Requirements 9.1, 9.3
|
||||
--
|
||||
-- 变更说明:
|
||||
-- CURRENT_DATE → dws.biz_date(NOW())
|
||||
-- date_trunc('month', CURRENT_DATE::timestamptz) → date_trunc('month', dws.biz_date(NOW())::timestamptz)
|
||||
--
|
||||
-- 注意:物化视图不支持 CREATE OR REPLACE,必须 DROP + CREATE 重建。
|
||||
-- 重建后数据为空(WITH DATA 会立即填充),需执行 REFRESH MATERIALIZED VIEW 或等待定时刷新。
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 助教日度明细物化视图(mv_dws_assistant_daily_detail_l1..l4)
|
||||
-- ============================================================
|
||||
|
||||
-- L1:昨日 + 今日(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l1 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '1 day'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L2:近 30 天(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l2 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '30 days'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L3:近 90 天(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l3 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '90 days'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L4:近 6 个月(不含当月,营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l4 AS
|
||||
SELECT * FROM dws.dws_assistant_daily_detail
|
||||
WHERE stat_date >= (date_trunc('month', dws.biz_date(NOW())::timestamp with time zone) - '6 mons'::interval)
|
||||
AND stat_date < date_trunc('month', dws.biz_date(NOW())::timestamp with time zone)
|
||||
WITH DATA;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 财务日度汇总物化视图(mv_dws_finance_daily_summary_l1..l4)
|
||||
-- ============================================================
|
||||
|
||||
-- L1:昨日 + 今日(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l1 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '1 day'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L2:近 30 天(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l2 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '30 days'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L3:近 90 天(营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l3 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (dws.biz_date(NOW()) - '90 days'::interval)
|
||||
WITH DATA;
|
||||
|
||||
-- L4:近 6 个月(不含当月,营业日口径)
|
||||
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
|
||||
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l4 AS
|
||||
SELECT * FROM dws.dws_finance_daily_summary
|
||||
WHERE stat_date >= (date_trunc('month', dws.biz_date(NOW())::timestamp with time zone) - '6 mons'::interval)
|
||||
AND stat_date < date_trunc('month', dws.biz_date(NOW())::timestamp with time zone)
|
||||
WITH DATA;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 重建物化视图索引
|
||||
-- ============================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l1
|
||||
ON dws.mv_dws_assistant_daily_detail_l1 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l2
|
||||
ON dws.mv_dws_assistant_daily_detail_l2 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l3
|
||||
ON dws.mv_dws_assistant_daily_detail_l3 USING btree (site_id, stat_date, assistant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_assistant_daily_l4
|
||||
ON dws.mv_dws_assistant_daily_detail_l4 USING btree (site_id, stat_date, assistant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l1
|
||||
ON dws.mv_dws_finance_daily_summary_l1 USING btree (site_id, stat_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l2
|
||||
ON dws.mv_dws_finance_daily_summary_l2 USING btree (site_id, stat_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l3
|
||||
ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_mv_finance_daily_l4
|
||||
ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 验证 SQL(迁移后执行):
|
||||
-- 1. 确认函数存在
|
||||
-- SELECT dws.biz_date(NOW());
|
||||
--
|
||||
-- 2. 确认 8 个物化视图已重建
|
||||
-- SELECT schemaname, matviewname FROM pg_matviews
|
||||
-- WHERE schemaname = 'dws' AND matviewname LIKE 'mv_dws_%'
|
||||
-- ORDER BY matviewname;
|
||||
--
|
||||
-- 3. 确认索引已创建
|
||||
-- SELECT indexname FROM pg_indexes
|
||||
-- WHERE schemaname = 'dws' AND tablename LIKE 'mv_dws_%'
|
||||
-- ORDER BY indexname;
|
||||
--
|
||||
-- 4. 确认视图定义中包含 biz_date
|
||||
-- SELECT matviewname, definition FROM pg_matviews
|
||||
-- WHERE schemaname = 'dws' AND matviewname LIKE 'mv_dws_%'
|
||||
-- AND definition LIKE '%biz_date%';
|
||||
|
||||
-- 回滚(恢复为自然日口径):
|
||||
-- BEGIN;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
|
||||
-- DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
|
||||
-- -- 然后用 scripts/migrate/migrate_finalize.py 中的原始定义重建
|
||||
-- COMMIT;
|
||||
@@ -0,0 +1,63 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:dws_assistant_order_contribution.tenant_id INTEGER → BIGINT
|
||||
-- 原因:飞球 tenant_id 值域(如 2790683160709957)远超 int4 上限(~21 亿)
|
||||
-- 依赖:app.v_dws_assistant_order_contribution 视图需先 DROP 再重建
|
||||
-- 日期:2026-03-03
|
||||
-- 目标库:etl_feiqiu / test_etl_feiqiu
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. DROP 依赖的 RLS 视图
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
|
||||
|
||||
-- 2. ALTER 列类型
|
||||
ALTER TABLE dws.dws_assistant_order_contribution
|
||||
ALTER COLUMN tenant_id TYPE bigint;
|
||||
|
||||
-- 3. 重建 RLS 视图
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
SELECT * FROM dws.dws_assistant_order_contribution
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本
|
||||
-- =============================================================================
|
||||
-- BEGIN;
|
||||
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
|
||||
-- ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN tenant_id TYPE integer;
|
||||
-- CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
-- SELECT * FROM dws.dws_assistant_order_contribution
|
||||
-- WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
-- GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
|
||||
-- COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认列类型已变更
|
||||
-- SELECT column_name, data_type, udt_name
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'dws'
|
||||
-- AND table_name = 'dws_assistant_order_contribution'
|
||||
-- AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint', udt_name = 'int8'
|
||||
|
||||
-- 2. 确认视图已重建
|
||||
-- SELECT table_schema, table_name
|
||||
-- FROM information_schema.views
|
||||
-- WHERE table_schema = 'app'
|
||||
-- AND table_name = 'v_dws_assistant_order_contribution';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认视图中 tenant_id 类型正确
|
||||
-- SELECT column_name, data_type
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'app'
|
||||
-- AND table_name = 'v_dws_assistant_order_contribution'
|
||||
-- AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint'
|
||||
@@ -0,0 +1,27 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:dim_groupbuy_package_ex 新增团购详情字段
|
||||
-- 日期:2026-03-05
|
||||
-- 说明:从团购详情接口(QueryPackageCouponInfo)提取的 4 个 JSONB 字段,
|
||||
-- 补充可用台区、助教服务、关联门店等维度信息
|
||||
-- 需求:需求 4 验收标准 1
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE dwd.dim_groupbuy_package_ex
|
||||
ADD COLUMN IF NOT EXISTS table_area_ids JSONB,
|
||||
ADD COLUMN IF NOT EXISTS table_area_names JSONB,
|
||||
ADD COLUMN IF NOT EXISTS assistant_services JSONB,
|
||||
ADD COLUMN IF NOT EXISTS groupon_site_infos JSONB;
|
||||
|
||||
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.table_area_ids IS '可用台区 ID 列表(来自详情接口 tableAreaId)';
|
||||
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.table_area_names IS '可用台区名称列表(来自详情接口 tableAreaNameList)';
|
||||
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.assistant_services IS '助教服务关联(来自详情接口 packageCouponAssistants)';
|
||||
COMMENT ON COLUMN dwd.dim_groupbuy_package_ex.groupon_site_infos IS '关联门店信息(来自详情接口 grouponSiteInfos)';
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本(如需撤销)
|
||||
-- ALTER TABLE dwd.dim_groupbuy_package_ex
|
||||
-- DROP COLUMN IF EXISTS table_area_ids,
|
||||
-- DROP COLUMN IF EXISTS table_area_names,
|
||||
-- DROP COLUMN IF EXISTS assistant_services,
|
||||
-- DROP COLUMN IF EXISTS groupon_site_infos;
|
||||
-- =============================================================================
|
||||
@@ -0,0 +1,102 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:cfg_area_category 新增字段 + VIP 包厢拆分 + 显示名称更新
|
||||
-- 日期:2026-03-07
|
||||
-- 说明:
|
||||
-- 1. 新增 source_table_name、display_name、short_name 三个字段
|
||||
-- 2. 删除 BILLIARD_VIP 类型,VIP包厢默认归 BILLIARD,V5 单独归 SNOOKER
|
||||
-- 3. 更新四大项目类型的 display_name 和 short_name
|
||||
-- 4. 更新模糊匹配规则(%VIP% 改为 BILLIARD)
|
||||
-- 回滚:见文件末尾 ROLLBACK 部分
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. 新增字段
|
||||
ALTER TABLE dws.cfg_area_category
|
||||
ADD COLUMN IF NOT EXISTS source_table_name VARCHAR(100) DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS display_name VARCHAR(50) DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS short_name VARCHAR(20) DEFAULT NULL;
|
||||
|
||||
-- 1.1 唯一约束从 (source_area_name) 改为 (source_area_name, source_table_name)
|
||||
-- 支持同一台区下按台桌名称细分映射
|
||||
ALTER TABLE dws.cfg_area_category DROP CONSTRAINT IF EXISTS uk_cfg_area_category;
|
||||
CREATE UNIQUE INDEX uk_cfg_area_category
|
||||
ON dws.cfg_area_category (source_area_name, COALESCE(source_table_name, ''));
|
||||
|
||||
|
||||
-- 2. 为现有记录填充 display_name 和 short_name
|
||||
UPDATE dws.cfg_area_category SET display_name = '🎱 中式/追分', short_name = '🎱'
|
||||
WHERE category_code = 'BILLIARD';
|
||||
UPDATE dws.cfg_area_category SET display_name = '斯诺克', short_name = '斯'
|
||||
WHERE category_code = 'SNOOKER';
|
||||
UPDATE dws.cfg_area_category SET display_name = '🀄 麻将/棋牌', short_name = '🀄'
|
||||
WHERE category_code = 'MAHJONG';
|
||||
UPDATE dws.cfg_area_category SET display_name = '🎤 团建/K歌', short_name = '🎤'
|
||||
WHERE category_code = 'KTV';
|
||||
UPDATE dws.cfg_area_category SET display_name = '补时长', short_name = '补'
|
||||
WHERE category_code = 'SPECIAL';
|
||||
UPDATE dws.cfg_area_category SET display_name = '其他', short_name = '他'
|
||||
WHERE category_code = 'OTHER';
|
||||
|
||||
-- 3. VIP包厢拆分:将原 BILLIARD_VIP 精确匹配改为 BILLIARD
|
||||
UPDATE dws.cfg_area_category
|
||||
SET category_code = 'BILLIARD',
|
||||
category_name = '🎱 中式/追分',
|
||||
display_name = '🎱 中式/追分',
|
||||
short_name = '🎱',
|
||||
description = '台球VIP包厢(V1-V4中八)→ 归入中式/追分',
|
||||
updated_at = NOW()
|
||||
WHERE source_area_name = 'VIP包厢' AND match_type = 'EXACT';
|
||||
|
||||
-- 4. 新增 V5 → SNOOKER 的台桌级映射(优先级高于区域级)
|
||||
INSERT INTO dws.cfg_area_category (
|
||||
source_area_name, source_table_name, category_code, category_name,
|
||||
display_name, short_name,
|
||||
match_type, match_priority, is_active, description
|
||||
) VALUES (
|
||||
'VIP包厢', 'V5', 'SNOOKER', '斯诺克',
|
||||
'斯诺克', '斯',
|
||||
'EXACT', 5, TRUE,
|
||||
'VIP包厢V5台→斯诺克(台桌级精确匹配,优先级高于区域级)'
|
||||
);
|
||||
|
||||
-- 5. 模糊匹配 %VIP% 改为 BILLIARD(原为 BILLIARD_VIP)
|
||||
UPDATE dws.cfg_area_category
|
||||
SET category_code = 'BILLIARD',
|
||||
category_name = '🎱 中式/追分',
|
||||
display_name = '🎱 中式/追分',
|
||||
short_name = '🎱',
|
||||
description = '模糊匹配:包含"VIP"的区域→归入中式/追分',
|
||||
updated_at = NOW()
|
||||
WHERE source_area_name = '%VIP%' AND match_type = 'LIKE';
|
||||
|
||||
-- 6. 更新所有分类的 category_name 为新显示名
|
||||
UPDATE dws.cfg_area_category SET category_name = '🎱 中式/追分', updated_at = NOW()
|
||||
WHERE category_code = 'BILLIARD' AND category_name != '🎱 中式/追分';
|
||||
UPDATE dws.cfg_area_category SET category_name = '斯诺克', updated_at = NOW()
|
||||
WHERE category_code = 'SNOOKER' AND category_name != '斯诺克';
|
||||
UPDATE dws.cfg_area_category SET category_name = '🀄 麻将/棋牌', updated_at = NOW()
|
||||
WHERE category_code = 'MAHJONG' AND category_name != '🀄 麻将/棋牌';
|
||||
UPDATE dws.cfg_area_category SET category_name = '🎤 团建/K歌', updated_at = NOW()
|
||||
WHERE category_code = 'KTV' AND category_name != '🎤 团建/K歌';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- ROLLBACK(如需回滚,手动执行以下语句)
|
||||
-- =============================================================================
|
||||
-- BEGIN;
|
||||
-- DELETE FROM dws.cfg_area_category WHERE source_table_name = 'V5';
|
||||
-- UPDATE dws.cfg_area_category SET category_code = 'BILLIARD_VIP', category_name = '台球VIP',
|
||||
-- description = '台球VIP:VIP包厢(4台)- V1-V4中八, V5斯诺克'
|
||||
-- WHERE source_area_name = 'VIP包厢' AND match_type = 'EXACT';
|
||||
-- UPDATE dws.cfg_area_category SET category_code = 'BILLIARD_VIP', category_name = '台球VIP'
|
||||
-- WHERE source_area_name = '%VIP%' AND match_type = 'LIKE';
|
||||
-- UPDATE dws.cfg_area_category SET category_name = '台球散台' WHERE category_code = 'BILLIARD';
|
||||
-- UPDATE dws.cfg_area_category SET category_name = '斯诺克' WHERE category_code = 'SNOOKER';
|
||||
-- UPDATE dws.cfg_area_category SET category_name = '麻将棋牌' WHERE category_code = 'MAHJONG';
|
||||
-- UPDATE dws.cfg_area_category SET category_name = 'K歌娱乐' WHERE category_code = 'KTV';
|
||||
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS source_table_name;
|
||||
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS display_name;
|
||||
-- ALTER TABLE dws.cfg_area_category DROP COLUMN IF EXISTS short_name;
|
||||
-- COMMIT;
|
||||
@@ -0,0 +1,95 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:创建项目标签表
|
||||
-- 日期:2026-03-07
|
||||
-- 说明:新建 dws_assistant_project_tag 和 dws_member_project_tag 两张表,
|
||||
-- 存储助教和客户按项目类型的时长占比标签
|
||||
-- 依赖:cfg_area_category(2026-03-07 已更新,含 display_name/short_name)
|
||||
-- =============================================================================
|
||||
|
||||
-- 1. 助教项目标签表
|
||||
CREATE TABLE IF NOT EXISTS dws.dws_assistant_project_tag (
|
||||
id BIGSERIAL NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT NOT NULL,
|
||||
time_window VARCHAR(40) NOT NULL, -- TimeWindow 枚举值
|
||||
category_code VARCHAR(30) NOT NULL, -- BILLIARD/SNOOKER/MAHJONG/KTV
|
||||
category_name VARCHAR(50) NOT NULL, -- 显示名称(如 🎱 中式/追分)
|
||||
short_name VARCHAR(10) NOT NULL, -- 简写(如 🎱)
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 该项目总工作时长(秒)
|
||||
total_seconds BIGINT NOT NULL DEFAULT 0, -- 所有四大项目总时长(秒)
|
||||
percentage NUMERIC(5,4) NOT NULL DEFAULT 0, -- 占比(0~1)
|
||||
is_tagged BOOLEAN NOT NULL DEFAULT FALSE, -- 占比≥25% 则为 TRUE
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT pk_dws_assistant_project_tag PRIMARY KEY (id),
|
||||
CONSTRAINT uk_dws_assistant_project_tag
|
||||
UNIQUE (site_id, assistant_id, time_window, category_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_assistant_project_tag IS '助教项目标签:按时间窗口计算各项目时长占比,≥25%分配标签';
|
||||
COMMENT ON COLUMN dws.dws_assistant_project_tag.time_window IS '时间窗口:THIS_MONTH/THIS_QUARTER/LAST_MONTH/LAST_3_MONTHS_EXCL_CURRENT/LAST_QUARTER/LAST_6_MONTHS';
|
||||
COMMENT ON COLUMN dws.dws_assistant_project_tag.is_tagged IS '占比≥0.25时为TRUE,表示该助教拥有此项目标签';
|
||||
|
||||
-- 2. 客户项目标签表
|
||||
CREATE TABLE IF NOT EXISTS dws.dws_member_project_tag (
|
||||
id BIGSERIAL NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
member_id BIGINT NOT NULL,
|
||||
time_window VARCHAR(40) NOT NULL, -- TimeWindow 枚举值
|
||||
category_code VARCHAR(30) NOT NULL, -- BILLIARD/SNOOKER/MAHJONG/KTV
|
||||
category_name VARCHAR(50) NOT NULL, -- 显示名称
|
||||
short_name VARCHAR(10) NOT NULL, -- 简写
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 该项目总计费时长(秒)
|
||||
total_seconds BIGINT NOT NULL DEFAULT 0, -- 所有四大项目总时长(秒)
|
||||
percentage NUMERIC(5,4) NOT NULL DEFAULT 0, -- 占比(0~1)
|
||||
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT pk_dws_member_project_tag PRIMARY KEY (id),
|
||||
CONSTRAINT uk_dws_member_project_tag
|
||||
UNIQUE (site_id, member_id, time_window, category_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_member_project_tag IS '客户项目标签:按时间窗口计算各项目消费时长占比,≥25%分配标签';
|
||||
COMMENT ON COLUMN dws.dws_member_project_tag.time_window IS '时间窗口:LAST_30_DAYS/LAST_60_DAYS';
|
||||
COMMENT ON COLUMN dws.dws_member_project_tag.is_tagged IS '占比≥0.25时为TRUE,表示该客户拥有此项目标签';
|
||||
|
||||
-- 3. 索引(加速看板查询:按 site_id + time_window 筛选 is_tagged=TRUE 的标签)
|
||||
CREATE INDEX IF NOT EXISTS idx_apt_site_window_tagged
|
||||
ON dws.dws_assistant_project_tag (site_id, time_window)
|
||||
WHERE is_tagged = TRUE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mpt_site_window_tagged
|
||||
ON dws.dws_member_project_tag (site_id, time_window)
|
||||
WHERE is_tagged = TRUE;
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证
|
||||
-- =============================================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
v_apt_exists BOOLEAN;
|
||||
v_mpt_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS(SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'dws' AND table_name = 'dws_assistant_project_tag')
|
||||
INTO v_apt_exists;
|
||||
|
||||
SELECT EXISTS(SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'dws' AND table_name = 'dws_member_project_tag')
|
||||
INTO v_mpt_exists;
|
||||
|
||||
IF NOT v_apt_exists THEN
|
||||
RAISE EXCEPTION 'dws_assistant_project_tag 创建失败';
|
||||
END IF;
|
||||
IF NOT v_mpt_exists THEN
|
||||
RAISE EXCEPTION 'dws_member_project_tag 创建失败';
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE '✅ 两张项目标签表创建成功';
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,67 @@
|
||||
-- 迁移:统一 DWS 层储值卡/充值卡支付字段命名
|
||||
-- 日期:2026-03-07
|
||||
-- 关联:账务构成排查发现 cash_card_pay/cash_card_consume 语义歧义
|
||||
-- cash_card_pay 取 balance_amount(含赠送卡),cash_card_consume 取 recharge_card_amount(仅现金卡)
|
||||
-- 幂等:RENAME + ADD 均为幂等操作(已存在则跳过)
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. dws_member_visit_detail:cash_card_pay → balance_pay + 新增 recharge_card_pay
|
||||
-- =============================================================================
|
||||
|
||||
-- 1a. 重命名 cash_card_pay → balance_pay(储值卡总额 = recharge + gift)
|
||||
ALTER TABLE dws.dws_member_visit_detail
|
||||
RENAME COLUMN cash_card_pay TO balance_pay;
|
||||
|
||||
-- 1b. 新增 recharge_card_pay(现金充值卡支付,balance_pay 的子项)
|
||||
ALTER TABLE dws.dws_member_visit_detail
|
||||
ADD COLUMN IF NOT EXISTS recharge_card_pay NUMERIC(12,2) NOT NULL DEFAULT 0;
|
||||
|
||||
-- 1c. 回填 recharge_card_pay = balance_pay - gift_card_pay
|
||||
UPDATE dws.dws_member_visit_detail
|
||||
SET recharge_card_pay = balance_pay - gift_card_pay
|
||||
WHERE recharge_card_pay = 0 AND balance_pay > 0;
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. dws_finance_daily_summary:cash_card_consume → recharge_card_consume
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE dws.dws_finance_daily_summary
|
||||
RENAME COLUMN cash_card_consume TO recharge_card_consume;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认 dws_member_visit_detail 字段存在
|
||||
-- SELECT column_name FROM information_schema.columns
|
||||
-- WHERE table_schema = 'dws' AND table_name = 'dws_member_visit_detail'
|
||||
-- AND column_name IN ('balance_pay', 'recharge_card_pay', 'gift_card_pay')
|
||||
-- ORDER BY column_name;
|
||||
-- 预期:3 行(balance_pay, gift_card_pay, recharge_card_pay)
|
||||
|
||||
-- 2. 确认 cash_card_pay 已不存在
|
||||
-- SELECT column_name FROM information_schema.columns
|
||||
-- WHERE table_schema = 'dws' AND table_name = 'dws_member_visit_detail'
|
||||
-- AND column_name = 'cash_card_pay';
|
||||
-- 预期:0 行
|
||||
|
||||
-- 3. 确认 dws_finance_daily_summary 字段重命名
|
||||
-- SELECT column_name FROM information_schema.columns
|
||||
-- WHERE table_schema = 'dws' AND table_name = 'dws_finance_daily_summary'
|
||||
-- AND column_name IN ('recharge_card_consume', 'gift_card_consume', 'card_consume_total');
|
||||
-- 预期:3 行
|
||||
|
||||
-- 4. 确认 cash_card_consume 已不存在
|
||||
-- SELECT column_name FROM information_schema.columns
|
||||
-- WHERE table_schema = 'dws' AND table_name = 'dws_finance_daily_summary'
|
||||
-- AND column_name = 'cash_card_consume';
|
||||
-- 预期:0 行
|
||||
|
||||
-- 5. 验证 recharge_card_pay 回填正确(balance_pay = recharge_card_pay + gift_card_pay)
|
||||
-- SELECT COUNT(*) AS mismatch_count
|
||||
-- FROM dws.dws_member_visit_detail
|
||||
-- WHERE ABS(balance_pay - recharge_card_pay - gift_card_pay) > 0.01;
|
||||
-- 预期:0
|
||||
@@ -0,0 +1,58 @@
|
||||
-- 迁移:DWS 层 ratio/margin/multiplier 字段精度扩展
|
||||
-- 原因:numeric(5,4) 只能存 ±0.9999,当比率 ≥ 1 或 < -1 时溢出(P1 已爆 gross_margin)
|
||||
-- 日期:2026-03-01
|
||||
-- 注意:需先 DROP 依赖视图,ALTER 后重建
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. DROP 依赖视图(app schema 的 RLS 视图)
|
||||
DROP VIEW IF EXISTS app.v_cfg_performance_tier CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_finance_analysis CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_recharge_commission CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_salary_calc CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_finance_discount_detail CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_finance_income_structure CASCADE;
|
||||
DROP VIEW IF EXISTS app.v_dws_member_assistant_intimacy CASCADE;
|
||||
|
||||
-- 2. ALTER 列类型:numeric(5,4)/numeric(6,4) → numeric(7,4)
|
||||
ALTER TABLE dws.cfg_performance_tier
|
||||
ALTER COLUMN bonus_deduction_ratio TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_assistant_finance_analysis
|
||||
ALTER COLUMN gross_margin TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_assistant_recharge_commission
|
||||
ALTER COLUMN commission_ratio TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc
|
||||
ALTER COLUMN bonus_deduction_ratio TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_finance_discount_detail
|
||||
ALTER COLUMN discount_ratio TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_finance_income_structure
|
||||
ALTER COLUMN income_ratio TYPE numeric(7,4);
|
||||
ALTER TABLE dws.dws_member_assistant_intimacy
|
||||
ALTER COLUMN burst_multiplier TYPE numeric(7,4);
|
||||
|
||||
-- 3. 重建视图(使用 pg_get_viewdef 保存的原始定义)
|
||||
-- 注意:实际执行时应从 _p1_migrate_with_views.py 自动保存/重建
|
||||
-- 以下为手动重建模板,视图定义为 SELECT * FROM dws.xxx WHERE site_id = ...
|
||||
CREATE OR REPLACE VIEW app.v_cfg_performance_tier AS
|
||||
SELECT * FROM dws.cfg_performance_tier
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_finance_analysis AS
|
||||
SELECT * FROM dws.dws_assistant_finance_analysis
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_recharge_commission AS
|
||||
SELECT * FROM dws.dws_assistant_recharge_commission
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_salary_calc AS
|
||||
SELECT * FROM dws.dws_assistant_salary_calc
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_discount_detail AS
|
||||
SELECT * FROM dws.dws_finance_discount_detail
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_income_structure AS
|
||||
SELECT * FROM dws.dws_finance_income_structure
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_assistant_intimacy AS
|
||||
SELECT * FROM dws.dws_member_assistant_intimacy
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,28 @@
|
||||
-- 回滚:DWS 层 ratio/margin/multiplier 字段精度还原
|
||||
-- 注意:如果已有数据超出原精度范围,回滚会失败,需先清理数据
|
||||
-- 日期:2026-03-01
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE dws.cfg_performance_tier
|
||||
ALTER COLUMN bonus_deduction_ratio TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_assistant_finance_analysis
|
||||
ALTER COLUMN gross_margin TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_assistant_recharge_commission
|
||||
ALTER COLUMN commission_ratio TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_assistant_salary_calc
|
||||
ALTER COLUMN bonus_deduction_ratio TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_finance_discount_detail
|
||||
ALTER COLUMN discount_ratio TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_finance_income_structure
|
||||
ALTER COLUMN income_ratio TYPE numeric(5,4);
|
||||
|
||||
ALTER TABLE dws.dws_member_spending_power_index
|
||||
ALTER COLUMN burst_multiplier TYPE numeric(6,4);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 迁移:ods.goods_stock_summary 加 siteid 列
|
||||
-- 原因:API 返回记录不含 siteId,但 DWD 层需要 site_id 做门店隔离
|
||||
-- ODS 入库时从 app.store_id 注入
|
||||
-- 日期:2026-03-01
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE ods.goods_stock_summary
|
||||
ADD COLUMN IF NOT EXISTS siteid bigint;
|
||||
|
||||
-- 回填已有数据(单门店部署,所有记录属于同一 store_id)
|
||||
-- 实际 store_id 值需从 .env 的 STORE_ID 获取,此处用子查询从 goods_stock_movements 推断
|
||||
UPDATE ods.goods_stock_summary s
|
||||
SET siteid = (
|
||||
SELECT DISTINCT m.siteid
|
||||
FROM ods.goods_stock_movements m
|
||||
WHERE m.siteid IS NOT NULL
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE s.siteid IS NULL;
|
||||
|
||||
COMMIT;
|
||||
94
db/etl_feiqiu/ods/group_buy_package_details.sql
Normal file
94
db/etl_feiqiu/ods/group_buy_package_details.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- ============================================================================
|
||||
-- 表:ods.group_buy_package_details
|
||||
-- 来源:QueryPackageCouponInfo 详情接口
|
||||
-- 说明:团购套餐详情 ODS 层,存储每个 couponId 的详情原始数据
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ods.group_buy_package_details (
|
||||
-- 主键
|
||||
coupon_id BIGINT NOT NULL,
|
||||
|
||||
-- 结构化业务字段(来自 data.groupPurchasePackage)
|
||||
package_name TEXT,
|
||||
duration INTEGER,
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
add_start_clock TEXT,
|
||||
add_end_clock TEXT,
|
||||
is_enabled INTEGER,
|
||||
is_delete INTEGER,
|
||||
site_id BIGINT,
|
||||
tenant_id BIGINT,
|
||||
create_time TIMESTAMPTZ,
|
||||
creator_name TEXT,
|
||||
|
||||
-- JSONB 数组字段
|
||||
table_area_ids JSONB,
|
||||
table_area_names JSONB,
|
||||
assistant_services JSONB,
|
||||
groupon_site_infos JSONB,
|
||||
package_services JSONB,
|
||||
coupon_details_list JSONB,
|
||||
|
||||
-- ETL 元数据
|
||||
content_hash TEXT,
|
||||
payload JSONB,
|
||||
fetched_at TIMESTAMPTZ DEFAULT now(),
|
||||
|
||||
-- 约束
|
||||
CONSTRAINT pk_group_buy_package_details PRIMARY KEY (coupon_id)
|
||||
);
|
||||
|
||||
-- 表注释
|
||||
COMMENT ON TABLE ods.group_buy_package_details
|
||||
IS '团购套餐详情 ODS:QueryPackageCouponInfo 原始数据';
|
||||
|
||||
-- 主键 / 结构化字段注释
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.coupon_id
|
||||
IS '团购套餐 ID(= groupPurchasePackage.id),主键';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.package_name
|
||||
IS '团购套餐名称';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.duration
|
||||
IS '台费计时时长(秒)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.start_time
|
||||
IS '可用日期开始';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.end_time
|
||||
IS '可用日期结束';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.add_start_clock
|
||||
IS '可用时段开始(如 "00:00:00")';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.add_end_clock
|
||||
IS '可用时段结束(如 "1.00:00:00")';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.is_enabled
|
||||
IS '是否启用(1=启用, 0=禁用)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.is_delete
|
||||
IS '是否已删除(1=已删除, 0=正常)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.site_id
|
||||
IS '店铺 ID';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.tenant_id
|
||||
IS '租户 ID';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.create_time
|
||||
IS '创建时间';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.creator_name
|
||||
IS '创建人';
|
||||
|
||||
-- JSONB 数组字段注释
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.table_area_ids
|
||||
IS '可用台区 ID 列表(来自 groupPurchasePackage.tableAreaId)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.table_area_names
|
||||
IS '可用台区名称列表(来自 groupPurchasePackage.tableAreaNameList)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.assistant_services
|
||||
IS '助教服务关联数组(来自 packageCouponAssistants,含 skillId/assistantLevel/assistantDuration)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.groupon_site_infos
|
||||
IS '关联门店信息数组(来自 grouponSiteInfos,含 siteId/siteName)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.package_services
|
||||
IS '套餐服务数组(来自 packagePackageService,待调研,可能为空)';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.coupon_details_list
|
||||
IS '券明细数组(来自 packageCouponDetailsList,待调研,可能为空)';
|
||||
|
||||
-- ETL 元数据字段注释
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.content_hash
|
||||
IS '业务字段内容哈希,用于变更检测';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.payload
|
||||
IS '详情接口完整原始 JSON 响应';
|
||||
COMMENT ON COLUMN ods.group_buy_package_details.fetched_at
|
||||
IS 'ETL 拉取时间戳';
|
||||
@@ -206,45 +206,71 @@ INSERT INTO dws.cfg_bonus_rules (
|
||||
-- =============================================================================
|
||||
-- 4. cfg_area_category - 台区分类映射
|
||||
-- 说明:
|
||||
-- - 将 dim_table.site_table_area_name 映射到财务报表区域分类
|
||||
-- - 映射规则: 精确匹配 > 模糊匹配 > 默认兜底
|
||||
-- - 将 dim_table.site_table_area_name 映射到项目分类
|
||||
-- - 新增 source_table_name 支持台桌级细分(如 VIP包厢 V5 → SNOOKER)
|
||||
-- - 映射规则: 台桌精确 > 区域精确 > 模糊匹配 > 默认兜底
|
||||
-- - 数据来源: BD_manual_dim_table.md 中的 site_table_area_name 实际分布
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_area_category RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_area_category (
|
||||
source_area_name, category_code, category_name,
|
||||
source_area_name, source_table_name, category_code, category_name,
|
||||
display_name, short_name,
|
||||
match_type, match_priority, is_active, description
|
||||
) VALUES
|
||||
-- VIP包厢台桌级映射(优先级最高)
|
||||
('VIP包厢', 'V5', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 5, TRUE,
|
||||
'VIP包厢V5台→斯诺克(台桌级精确匹配,优先级高于区域级)'),
|
||||
-- 台球散台(精确匹配)
|
||||
('A区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:A区(18台)- 中八/追分'),
|
||||
('B区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:B区(15台)- 中八/追分'),
|
||||
('C区', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:C区(6台)- 中八/追分'),
|
||||
('TV台', 'BILLIARD', '台球散台', 'EXACT', 10, TRUE, '台球散台:TV台(1台)- 中八/追分'),
|
||||
-- 台球VIP包厢
|
||||
('VIP包厢', 'BILLIARD_VIP', '台球VIP', 'EXACT', 10, TRUE, '台球VIP:VIP包厢(4台)- V1-V4中八, V5斯诺克'),
|
||||
('A区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
|
||||
'台球散台:A区(18台)- 中八/追分'),
|
||||
('B区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
|
||||
'台球散台:B区(15台)- 中八/追分'),
|
||||
('C区', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
|
||||
'台球散台:C区(6台)- 中八/追分'),
|
||||
('TV台', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
|
||||
'台球散台:TV台(1台)- 中八/追分'),
|
||||
-- VIP包厢区域级(V1-V4 归入中式/追分)
|
||||
('VIP包厢', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE,
|
||||
'台球VIP包厢(V1-V4中八)→ 归入中式/追分'),
|
||||
-- 斯诺克区
|
||||
('斯诺克区', 'SNOOKER', '斯诺克', 'EXACT', 10, TRUE, '斯诺克:斯诺克区(4台)'),
|
||||
('斯诺克区', NULL, 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE,
|
||||
'斯诺克:斯诺克区(4台)'),
|
||||
-- 麻将区
|
||||
('麻将房', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:麻将房(5台)'),
|
||||
('M7', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:M7(2台)'),
|
||||
('M8', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:M8(1台)'),
|
||||
('666', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:666(2台)'),
|
||||
('发财', 'MAHJONG', '麻将棋牌', 'EXACT', 10, TRUE, '麻将棋牌:发财(1台)'),
|
||||
('麻将房', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
|
||||
'麻将棋牌:麻将房(5台)'),
|
||||
('M7', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
|
||||
'麻将棋牌:M7(2台)'),
|
||||
('M8', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
|
||||
'麻将棋牌:M8(1台)'),
|
||||
('666', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
|
||||
'麻将棋牌:666(2台)'),
|
||||
('发财', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE,
|
||||
'麻将棋牌:发财(1台)'),
|
||||
-- KTV/K包
|
||||
('K包', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐:K包(4台)'),
|
||||
('k包活动区', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐:k包活动区(2台)'),
|
||||
('幸会158', 'KTV', 'K歌娱乐', 'EXACT', 10, TRUE, 'K歌娱乐:幸会158(2台)'),
|
||||
('K包', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
|
||||
'K歌娱乐:K包(4台)'),
|
||||
('k包活动区', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
|
||||
'K歌娱乐:k包活动区(2台)'),
|
||||
('幸会158', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE,
|
||||
'K歌娱乐:幸会158(2台)'),
|
||||
-- 特殊区域
|
||||
('补时长', 'SPECIAL', '补时长', 'EXACT', 10, TRUE, '特殊:补时长(7台)- 用于时长补录'),
|
||||
('补时长', NULL, 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE,
|
||||
'特殊:补时长(7台)- 用于时长补录'),
|
||||
-- 模糊匹配规则
|
||||
('%VIP%', 'BILLIARD_VIP', '台球VIP', 'LIKE', 50, TRUE, '模糊匹配:包含"VIP"的区域'),
|
||||
('%斯诺克%', 'SNOOKER', '斯诺克', 'LIKE', 50, TRUE, '模糊匹配:包含"斯诺克"的区域'),
|
||||
('%麻将%', 'MAHJONG', '麻将棋牌', 'LIKE', 50, TRUE, '模糊匹配:包含"麻将"的区域'),
|
||||
('%K包%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"K包"的区域'),
|
||||
('%KTV%', 'KTV', 'K歌娱乐', 'LIKE', 50, TRUE, '模糊匹配:包含"KTV"的区域'),
|
||||
('%VIP%', NULL, 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'LIKE', 50, TRUE,
|
||||
'模糊匹配:包含"VIP"的区域→归入中式/追分'),
|
||||
('%斯诺克%', NULL, 'SNOOKER', '斯诺克', '斯诺克', '斯', 'LIKE', 50, TRUE,
|
||||
'模糊匹配:包含"斯诺克"的区域'),
|
||||
('%麻将%', NULL, 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'LIKE', 50, TRUE,
|
||||
'模糊匹配:包含"麻将"的区域'),
|
||||
('%K包%', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'LIKE', 50, TRUE,
|
||||
'模糊匹配:包含"K包"的区域'),
|
||||
('%KTV%', NULL, 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'LIKE', 50, TRUE,
|
||||
'模糊匹配:包含"KTV"的区域'),
|
||||
-- 默认兜底
|
||||
('DEFAULT', 'OTHER', '其他', 'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他');
|
||||
('DEFAULT', NULL, 'OTHER', '其他', '其他', '他', 'DEFAULT', 999, TRUE,
|
||||
'兜底规则:无法匹配的区域归入其他');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
-- =============================================================================
|
||||
-- FDW 反向映射配置(生产环境)— 在 etl_feiqiu 数据库中执行
|
||||
-- 用途:通过 postgres_fdw 将 zqyy_app.member_birthday_manual 只读映射到 etl_feiqiu,
|
||||
-- 使 ETL DWS 任务无需直接连接业务库即可读取助教手动补录的会员生日数据。
|
||||
-- 用途:通过 postgres_fdw 将 zqyy_app.member_retention_clue 只读映射到 etl_feiqiu,
|
||||
-- 使 ETL DWS 任务无需直接连接业务库即可读取维客线索数据。
|
||||
-- 方向:etl_feiqiu → zqyy_app(与 setup_fdw.sql 的 zqyy_app → etl_feiqiu 方向相反)
|
||||
-- 前提:zqyy_app 数据库已部署 member_birthday_manual 表(见 C2 迁移脚本)
|
||||
-- 前提:zqyy_app 数据库已部署 member_retention_clue 表
|
||||
-- 测试环境版本:setup_fdw_reverse_test.sql(指向 test_zqyy_app)
|
||||
-- Requirements: 5.3
|
||||
-- CHANGE 2026-02-26 | member_birthday_manual → member_retention_clue(维客线索重构)
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
@@ -23,9 +23,6 @@ CREATE SERVER IF NOT EXISTS zqyy_app_server
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 3. 创建用户映射(只读角色)
|
||||
-- etl_user = etl_feiqiu 侧的 ETL 连接角色
|
||||
-- app_reader = zqyy_app 侧的只读角色
|
||||
-- 密码占位符 '***',部署时替换为真实凭据
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE USER MAPPING IF NOT EXISTS FOR etl_user
|
||||
SERVER zqyy_app_server
|
||||
@@ -37,29 +34,27 @@ CREATE USER MAPPING IF NOT EXISTS FOR etl_user
|
||||
CREATE SCHEMA IF NOT EXISTS fdw_app;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 5. 创建外部表:member_birthday_manual
|
||||
-- 映射 zqyy_app.public.member_birthday_manual,ETL 侧只读
|
||||
-- 列定义须与源表结构保持一致(见 C2 迁移脚本)
|
||||
-- 5. 创建外部表:member_retention_clue
|
||||
-- 映射 zqyy_app.public.member_retention_clue,ETL 侧只读
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE FOREIGN TABLE IF NOT EXISTS fdw_app.member_birthday_manual (
|
||||
CREATE FOREIGN TABLE IF NOT EXISTS fdw_app.member_retention_clue (
|
||||
id BIGINT,
|
||||
member_id BIGINT,
|
||||
birthday_value DATE,
|
||||
category VARCHAR(20),
|
||||
summary VARCHAR(200),
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ,
|
||||
source VARCHAR(20),
|
||||
site_id BIGINT
|
||||
) SERVER zqyy_app_server
|
||||
OPTIONS (schema_name 'public', table_name 'member_birthday_manual');
|
||||
OPTIONS (schema_name 'public', table_name 'member_retention_clue');
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 6. 授权:允许 etl_user 访问 fdw_app schema 及其外部表
|
||||
-- -----------------------------------------------------------------------------
|
||||
GRANT USAGE ON SCHEMA fdw_app TO etl_user;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_app TO etl_user;
|
||||
|
||||
-- 未来新增的外部表自动授权
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
|
||||
|
||||
@@ -69,7 +64,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
-- ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app REVOKE SELECT ON TABLES FROM etl_user;
|
||||
-- REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_app FROM etl_user;
|
||||
-- REVOKE USAGE ON SCHEMA fdw_app FROM etl_user;
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_app.member_birthday_manual;
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
-- DROP SCHEMA IF EXISTS fdw_app CASCADE;
|
||||
-- DROP USER MAPPING IF EXISTS FOR etl_user SERVER zqyy_app_server;
|
||||
-- DROP SERVER IF EXISTS zqyy_app_server CASCADE;
|
||||
@@ -78,34 +73,14 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认外部服务器 zqyy_app_server 存在
|
||||
-- SELECT srvname, srvowner::regrole, srvoptions
|
||||
-- FROM pg_foreign_server
|
||||
-- 1. 确认外部服务器存在
|
||||
-- SELECT srvname, srvoptions FROM pg_foreign_server
|
||||
-- WHERE srvname = 'zqyy_app_server';
|
||||
-- 预期:1 行,srvoptions 包含 dbname=zqyy_app
|
||||
|
||||
-- 2. 确认用户映射存在
|
||||
-- SELECT um.umid, s.srvname, um.umoptions
|
||||
-- FROM pg_user_mapping um
|
||||
-- JOIN pg_foreign_server s ON um.umserver = s.oid
|
||||
-- WHERE s.srvname = 'zqyy_app_server';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认 fdw_app schema 存在
|
||||
-- SELECT schema_name
|
||||
-- FROM information_schema.schemata
|
||||
-- WHERE schema_name = 'fdw_app';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 4. 确认外部表 fdw_app.member_birthday_manual 存在且列完整
|
||||
-- SELECT column_name, data_type
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fdw_app'
|
||||
-- AND table_name = 'member_birthday_manual'
|
||||
-- 2. 确认外部表列结构完整(9 列)
|
||||
-- SELECT column_name, data_type FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fdw_app' AND table_name = 'member_retention_clue'
|
||||
-- ORDER BY ordinal_position;
|
||||
-- 预期:8 列(id, member_id, birthday_value, recorded_by_assistant_id,
|
||||
-- recorded_by_name, recorded_at, source, site_id)
|
||||
|
||||
-- 5. 确认外部表可读取(需 zqyy_app 侧表已存在且有网络连通性)
|
||||
-- SELECT COUNT(*) FROM fdw_app.member_birthday_manual;
|
||||
-- 预期:返回行数(可能为 0)
|
||||
-- 3. 确认外部表可读取
|
||||
-- SELECT COUNT(*) FROM fdw_app.member_retention_clue;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
-- =============================================================================
|
||||
-- FDW 反向映射配置(测试环境)— 在 test_etl_feiqiu 数据库中执行
|
||||
-- 用途:通过 postgres_fdw 将 test_zqyy_app.member_birthday_manual 只读映射到
|
||||
-- test_etl_feiqiu,使 ETL DWS 任务在测试环境下可读取手动补录的会员生日数据。
|
||||
-- 用途:通过 postgres_fdw 将 test_zqyy_app.member_retention_clue 只读映射到
|
||||
-- test_etl_feiqiu,使 ETL DWS 任务在测试环境下可读取维客线索数据。
|
||||
-- 方向:test_etl_feiqiu → test_zqyy_app(与 setup_fdw_test.sql 方向相反)
|
||||
-- 前提:test_zqyy_app 数据库已部署 member_birthday_manual 表(见 C2 迁移脚本)
|
||||
-- 前提:test_zqyy_app 数据库已部署 member_retention_clue 表
|
||||
-- 基于 setup_fdw_reverse.sql,仅将目标库替换为测试库
|
||||
-- Requirements: 5.3
|
||||
-- CHANGE 2026-02-26 | member_birthday_manual → member_retention_clue(维客线索重构)
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
@@ -15,7 +15,6 @@ CREATE EXTENSION IF NOT EXISTS postgres_fdw;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2. 创建外部服务器(指向 test_zqyy_app 测试业务库)
|
||||
-- 部署时按实际环境替换 host / port
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE SERVER IF NOT EXISTS test_zqyy_app_server
|
||||
FOREIGN DATA WRAPPER postgres_fdw
|
||||
@@ -23,9 +22,6 @@ CREATE SERVER IF NOT EXISTS test_zqyy_app_server
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 3. 创建用户映射(只读角色)
|
||||
-- etl_user = test_etl_feiqiu 侧的 ETL 连接角色
|
||||
-- app_reader = test_zqyy_app 侧的只读角色
|
||||
-- 密码占位符 '***',部署时替换为真实凭据
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE USER MAPPING IF NOT EXISTS FOR etl_user
|
||||
SERVER test_zqyy_app_server
|
||||
@@ -37,29 +33,27 @@ CREATE USER MAPPING IF NOT EXISTS FOR etl_user
|
||||
CREATE SCHEMA IF NOT EXISTS fdw_app;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 5. 创建外部表:member_birthday_manual
|
||||
-- 映射 test_zqyy_app.public.member_birthday_manual,ETL 侧只读
|
||||
-- 列定义须与源表结构保持一致(见 C2 迁移脚本)
|
||||
-- 5. 创建外部表:member_retention_clue
|
||||
-- 映射 test_zqyy_app.public.member_retention_clue,ETL 侧只读
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE FOREIGN TABLE IF NOT EXISTS fdw_app.member_birthday_manual (
|
||||
CREATE FOREIGN TABLE IF NOT EXISTS fdw_app.member_retention_clue (
|
||||
id BIGINT,
|
||||
member_id BIGINT,
|
||||
birthday_value DATE,
|
||||
category VARCHAR(20),
|
||||
summary VARCHAR(200),
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ,
|
||||
source VARCHAR(20),
|
||||
site_id BIGINT
|
||||
) SERVER test_zqyy_app_server
|
||||
OPTIONS (schema_name 'public', table_name 'member_birthday_manual');
|
||||
OPTIONS (schema_name 'public', table_name 'member_retention_clue');
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 6. 授权:允许 etl_user 访问 fdw_app schema 及其外部表
|
||||
-- 6. 授权
|
||||
-- -----------------------------------------------------------------------------
|
||||
GRANT USAGE ON SCHEMA fdw_app TO etl_user;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_app TO etl_user;
|
||||
|
||||
-- 未来新增的外部表自动授权
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
|
||||
|
||||
@@ -69,7 +63,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
-- ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app REVOKE SELECT ON TABLES FROM etl_user;
|
||||
-- REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_app FROM etl_user;
|
||||
-- REVOKE USAGE ON SCHEMA fdw_app FROM etl_user;
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_app.member_birthday_manual;
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
-- DROP SCHEMA IF EXISTS fdw_app CASCADE;
|
||||
-- DROP USER MAPPING IF EXISTS FOR etl_user SERVER test_zqyy_app_server;
|
||||
-- DROP SERVER IF EXISTS test_zqyy_app_server CASCADE;
|
||||
@@ -78,34 +72,14 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认外部服务器 test_zqyy_app_server 存在
|
||||
-- SELECT srvname, srvowner::regrole, srvoptions
|
||||
-- FROM pg_foreign_server
|
||||
-- 1. 确认外部服务器存在
|
||||
-- SELECT srvname, srvoptions FROM pg_foreign_server
|
||||
-- WHERE srvname = 'test_zqyy_app_server';
|
||||
-- 预期:1 行,srvoptions 包含 dbname=test_zqyy_app
|
||||
|
||||
-- 2. 确认用户映射存在
|
||||
-- SELECT um.umid, s.srvname, um.umoptions
|
||||
-- FROM pg_user_mapping um
|
||||
-- JOIN pg_foreign_server s ON um.umserver = s.oid
|
||||
-- WHERE s.srvname = 'test_zqyy_app_server';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认 fdw_app schema 存在
|
||||
-- SELECT schema_name
|
||||
-- FROM information_schema.schemata
|
||||
-- WHERE schema_name = 'fdw_app';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 4. 确认外部表 fdw_app.member_birthday_manual 存在且列完整
|
||||
-- SELECT column_name, data_type
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fdw_app'
|
||||
-- AND table_name = 'member_birthday_manual'
|
||||
-- 2. 确认外部表列结构完整(9 列)
|
||||
-- SELECT column_name, data_type FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fdw_app' AND table_name = 'member_retention_clue'
|
||||
-- ORDER BY ordinal_position;
|
||||
-- 预期:8 列(id, member_id, birthday_value, recorded_by_assistant_id,
|
||||
-- recorded_by_name, recorded_at, source, site_id)
|
||||
|
||||
-- 5. 确认外部表可读取(需 test_zqyy_app 侧表已存在且有网络连通性)
|
||||
-- SELECT COUNT(*) FROM fdw_app.member_birthday_manual;
|
||||
-- 预期:返回行数(可能为 0)
|
||||
-- 3. 确认外部表可读取
|
||||
-- SELECT COUNT(*) FROM fdw_app.member_retention_clue;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_birthday_manual → member_retention_clue(维客线索重构)
|
||||
-- 日期:2026-02-26
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 1. 删除 member_birthday_manual 表(生日不再单独记录,归入维客线索"客户基础信息"大类)
|
||||
-- 2. 新建 member_retention_clue 表(维客线索:大类 + 摘要 + 详情)
|
||||
-- 影响:
|
||||
-- - 后端 API:/api/member-birthday 废弃,替换为 /api/retention-clue
|
||||
-- - ETL DWS:移除 FDW 读取 member_birthday_manual 的逻辑
|
||||
-- - FDW:fdw_app.member_birthday_manual 外部表需在 ETL 库侧同步删除
|
||||
-- 幂等:DROP IF EXISTS / CREATE IF NOT EXISTS,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 删除旧表 member_birthday_manual
|
||||
-- ============================================================
|
||||
DROP TABLE IF EXISTS member_birthday_manual CASCADE;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 创建 member_retention_clue 表
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS member_retention_clue (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
member_id BIGINT NOT NULL,
|
||||
category VARCHAR(20) NOT NULL
|
||||
CONSTRAINT chk_retention_clue_category CHECK (
|
||||
category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
),
|
||||
summary VARCHAR(200) NOT NULL,
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
site_id BIGINT NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE member_retention_clue
|
||||
IS '维客线索:助教为会员记录的销售/维护线索(大类 + 摘要 + 详情)';
|
||||
COMMENT ON COLUMN member_retention_clue.category
|
||||
IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
COMMENT ON COLUMN member_retention_clue.summary
|
||||
IS '摘要:重点信息';
|
||||
COMMENT ON COLUMN member_retention_clue.detail
|
||||
IS '详情:分析及扩展说明,可为空';
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建索引
|
||||
-- ============================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_member
|
||||
ON member_retention_clue (member_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_site
|
||||
ON member_retention_clue (site_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_category
|
||||
ON member_retention_clue (member_id, category);
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行,不在事务内)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- DROP TABLE IF EXISTS member_retention_clue CASCADE;
|
||||
-- -- 如需恢复旧表,执行 2026-02-22__C2_member_birthday_manual.sql
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认旧表已删除
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_birthday_manual';
|
||||
-- 预期:0 行
|
||||
|
||||
-- 2. 确认新表存在
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认列结构完整(9 列)
|
||||
-- SELECT column_name, data_type, is_nullable
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
|
||||
-- ORDER BY ordinal_position;
|
||||
-- 预期:id, member_id, category, summary, detail,
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id
|
||||
|
||||
-- 4. 确认 CHECK 约束存在
|
||||
-- SELECT conname, consrc FROM pg_constraint
|
||||
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
|
||||
-- 预期:1 行,chk_retention_clue_category
|
||||
|
||||
-- 5. 确认索引存在
|
||||
-- SELECT indexname FROM pg_indexes
|
||||
-- WHERE tablename = 'member_retention_clue';
|
||||
-- 预期:3 行(idx_retention_clue_member, idx_retention_clue_site, idx_retention_clue_category)
|
||||
-- + 1 行主键索引
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 迁移:为 auth.users.status 的 CHECK 约束添加 'new' 值
|
||||
-- 原因:新用户注册后初始状态为 'new'(尚未提交申请),提交申请后变为 'pending'
|
||||
-- 影响:后端 wx_login 新用户创建、dev_login mock 登录
|
||||
-- 回滚:见文件末尾
|
||||
|
||||
-- 1. 删除旧约束
|
||||
ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
|
||||
|
||||
-- 2. 添加包含 'new' 的新约束
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_status_check
|
||||
CHECK (status IN ('new', 'pending', 'approved', 'rejected', 'disabled'));
|
||||
|
||||
-- 3. 修改默认值:新用户初始状态为 'new'(尚未提交申请)
|
||||
ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'new';
|
||||
|
||||
-- ── 回滚 SQL ──
|
||||
-- ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'pending';
|
||||
-- ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
|
||||
-- ALTER TABLE auth.users ADD CONSTRAINT users_status_check
|
||||
-- CHECK (status IN ('pending', 'approved', 'rejected', 'disabled'));
|
||||
-- UPDATE auth.users SET status = 'pending' WHERE status = 'new';
|
||||
@@ -0,0 +1,52 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_retention_clue 新增 source 字段
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 区分维客线索来源:manual(助教手动)/ ai_consumption(应用3)/ ai_note(应用6)
|
||||
-- 影响:
|
||||
-- - 后端 API:POST /api/retention-clue 已引用 source 列,本次补齐 DDL
|
||||
-- - 已有数据自动填充 DEFAULT 'manual'
|
||||
-- 幂等:IF NOT EXISTS 检查列是否存在,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 仅在列不存在时添加(PostgreSQL 11+ 支持 IF NOT EXISTS)
|
||||
ALTER TABLE member_retention_clue
|
||||
ADD COLUMN IF NOT EXISTS source VARCHAR(20) NOT NULL DEFAULT 'manual';
|
||||
|
||||
COMMENT ON COLUMN member_retention_clue.source
|
||||
IS '线索来源:manual(助教手动)/ ai_consumption(应用3 消费分析)/ ai_note(应用6 备注分析)';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- ALTER TABLE member_retention_clue DROP COLUMN IF EXISTS source;
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认 source 列存在
|
||||
-- SELECT column_name, data_type, column_default, is_nullable
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'public'
|
||||
-- AND table_name = 'member_retention_clue'
|
||||
-- AND column_name = 'source';
|
||||
-- 预期:1 行,varchar, 'manual'::character varying, NO
|
||||
|
||||
-- 2. 确认已有数据的 source 值
|
||||
-- SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
|
||||
-- 预期:全部为 'manual'(或空表)
|
||||
|
||||
-- 3. 确认列注释
|
||||
-- SELECT col_description(
|
||||
-- (SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
|
||||
-- (SELECT ordinal_position FROM information_schema.columns
|
||||
-- WHERE table_name = 'member_retention_clue' AND column_name = 'source')
|
||||
-- );
|
||||
-- 预期:包含 'manual' / 'ai_consumption' / 'ai_note'
|
||||
107
db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql
Normal file
107
db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql
Normal file
@@ -0,0 +1,107 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:创建 biz Schema 业务表(coach_tasks / coach_task_history / notes / trigger_jobs)
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:test_zqyy_app(通过 APP_DB_DSN 连接)
|
||||
-- 说明:在 biz Schema 下创建助教任务系统和备注系统所需的 4 张业务表,
|
||||
-- 包含字段定义、约束、CHECK 约束、外键、部分唯一索引和查询索引。
|
||||
-- 前提:biz Schema 已由 2026-02-24__p1_create_auth_biz_schemas.sql 创建
|
||||
-- 需求:1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
|
||||
-- =============================================================================
|
||||
|
||||
-- 确保 biz Schema 存在(幂等)
|
||||
CREATE SCHEMA IF NOT EXISTS biz;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. biz.coach_tasks — 助教任务表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.coach_tasks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT NOT NULL,
|
||||
member_id BIGINT NOT NULL,
|
||||
task_type VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
priority_score NUMERIC(5,2),
|
||||
expires_at TIMESTAMPTZ,
|
||||
is_pinned BOOLEAN DEFAULT FALSE,
|
||||
abandon_reason TEXT,
|
||||
completed_at TIMESTAMPTZ,
|
||||
completed_task_type VARCHAR(50),
|
||||
parent_task_id BIGINT REFERENCES biz.coach_tasks(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 部分唯一索引:同一 (site_id, assistant_id, member_id, task_type) 下 active 任务最多一条
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_coach_tasks_site_assistant_member_type
|
||||
ON biz.coach_tasks (site_id, assistant_id, member_id, task_type)
|
||||
WHERE status = 'active';
|
||||
|
||||
-- 助教任务列表查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_coach_tasks_assistant_status
|
||||
ON biz.coach_tasks (site_id, assistant_id, status);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2. biz.coach_task_history — 任务变更历史表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.coach_task_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
|
||||
action VARCHAR(50) NOT NULL,
|
||||
old_status VARCHAR(20),
|
||||
new_status VARCHAR(20),
|
||||
old_task_type VARCHAR(50),
|
||||
new_task_type VARCHAR(50),
|
||||
detail JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. biz.notes — 统一备注表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.notes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
target_type VARCHAR(50) NOT NULL,
|
||||
target_id BIGINT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL DEFAULT 'normal',
|
||||
content TEXT NOT NULL,
|
||||
rating_service_willingness SMALLINT CHECK (rating_service_willingness BETWEEN 1 AND 5),
|
||||
rating_revisit_likelihood SMALLINT CHECK (rating_revisit_likelihood BETWEEN 1 AND 5),
|
||||
task_id BIGINT REFERENCES biz.coach_tasks(id),
|
||||
ai_score SMALLINT,
|
||||
ai_analysis TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 按目标查询备注索引
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_target
|
||||
ON biz.notes (site_id, target_type, target_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 4. biz.trigger_jobs — 触发器配置表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.trigger_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
job_type VARCHAR(100) NOT NULL,
|
||||
job_name VARCHAR(100) NOT NULL UNIQUE,
|
||||
trigger_condition VARCHAR(20) NOT NULL,
|
||||
trigger_config JSONB NOT NULL,
|
||||
last_run_at TIMESTAMPTZ,
|
||||
next_run_at TIMESTAMPTZ,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'enabled',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本(按逆序执行)
|
||||
-- =============================================================================
|
||||
-- DROP INDEX IF EXISTS biz.idx_notes_target;
|
||||
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_assistant_status;
|
||||
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_site_assistant_member_type;
|
||||
-- DROP TABLE IF EXISTS biz.trigger_jobs;
|
||||
-- DROP TABLE IF EXISTS biz.notes;
|
||||
-- DROP TABLE IF EXISTS biz.coach_task_history;
|
||||
-- DROP TABLE IF EXISTS biz.coach_tasks;
|
||||
43
db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql
Normal file
43
db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:预置触发器配置种子数据
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:test_zqyy_app(通过 APP_DB_DSN 连接)
|
||||
-- 说明:在 biz.trigger_jobs 表中插入 4 条核心触发器配置,
|
||||
-- 驱动任务生成、有效期检查、召回完成检测、备注回溯重分类。
|
||||
-- 前提:biz.trigger_jobs 表已由 2026-02-27__p4_create_biz_tables.sql 创建
|
||||
-- 需求:2.1, 2.2, 2.3, 2.4, 2.5
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO biz.trigger_jobs (job_type, job_name, trigger_condition, trigger_config, next_run_at)
|
||||
VALUES
|
||||
-- 1. task_generator:每日凌晨 4:00 运行,基于 WBI/NCI/RS 指数为助教生成任务
|
||||
('task_generator', 'task_generator', 'cron',
|
||||
'{"cron_expression": "0 4 * * *"}',
|
||||
(CURRENT_DATE + 1) + INTERVAL '4 hours'),
|
||||
|
||||
-- 2. task_expiry_check:每小时运行,检查 expires_at 到期的任务并标记 inactive
|
||||
('task_expiry_check', 'task_expiry_check', 'interval',
|
||||
'{"interval_seconds": 3600}',
|
||||
NOW() + INTERVAL '1 hour'),
|
||||
|
||||
-- 3. recall_completion_check:ETL 数据更新后触发,检测助教服务记录匹配活跃任务
|
||||
('recall_completion_check', 'recall_completion_check', 'event',
|
||||
'{"event_name": "etl_data_updated"}',
|
||||
NULL),
|
||||
|
||||
-- 4. note_reclassify_backfill:召回完成后触发,回溯重分类普通备注为回访备注
|
||||
('note_reclassify_backfill', 'note_reclassify_backfill', 'event',
|
||||
'{"event_name": "recall_completed"}',
|
||||
NULL)
|
||||
ON CONFLICT (job_name) DO NOTHING;
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本
|
||||
-- =============================================================================
|
||||
-- DELETE FROM biz.trigger_jobs
|
||||
-- WHERE job_name IN (
|
||||
-- 'task_generator',
|
||||
-- 'task_expiry_check',
|
||||
-- 'recall_completion_check',
|
||||
-- 'note_reclassify_backfill'
|
||||
-- );
|
||||
@@ -0,0 +1,50 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:auth.site_code_mapping.tenant_id INTEGER → BIGINT
|
||||
-- 原因:飞球 tenant_id 值域(如 2790683160709957)远超 int4 上限(~21 亿)
|
||||
-- 日期:2026-03-03
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 注意:该列无外键、无索引依赖,可直接 ALTER
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. ALTER 列类型
|
||||
ALTER TABLE auth.site_code_mapping
|
||||
ALTER COLUMN tenant_id TYPE bigint;
|
||||
|
||||
-- 2. 刷新 FDW 外部表(ETL 库 tenant_id 变更后需重新导入)
|
||||
-- 注意:此步骤需在 ETL 库迁移完成后执行
|
||||
-- 如果 FDW 外部表类型不匹配,需要重新 IMPORT FOREIGN SCHEMA
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
|
||||
-- IMPORT FOREIGN SCHEMA app
|
||||
-- LIMIT TO (v_dws_assistant_order_contribution)
|
||||
-- FROM SERVER test_etl_feiqiu_server
|
||||
-- INTO fdw_etl;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本
|
||||
-- =============================================================================
|
||||
-- BEGIN;
|
||||
-- ALTER TABLE auth.site_code_mapping ALTER COLUMN tenant_id TYPE integer;
|
||||
-- COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认列类型已变更
|
||||
-- SELECT column_name, data_type, udt_name
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'auth'
|
||||
-- AND table_name = 'site_code_mapping'
|
||||
-- AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint', udt_name = 'int8'
|
||||
|
||||
-- 2. 确认现有数据完整
|
||||
-- SELECT count(*) FROM auth.site_code_mapping;
|
||||
-- 预期:行数与迁移前一致
|
||||
|
||||
-- 3. 确认可插入大值 tenant_id
|
||||
-- SELECT 2790683160709957::bigint;
|
||||
-- 预期:返回 2790683160709957(不报溢出错误)
|
||||
@@ -0,0 +1,11 @@
|
||||
-- 2026-03-07 | 多实例任务隔离:task_queue 增加 enqueued_by 列
|
||||
-- 背景:发现有另一台机器(宿主机 D 盘部署)的后端也在消费同一个 task_queue,
|
||||
-- 导致任务被错误实例执行(路径不匹配 → UTF-8 解码失败)。
|
||||
-- 通过 enqueued_by 列记录入队实例的 hostname,process_loop 只消费自己入队的任务。
|
||||
|
||||
ALTER TABLE task_queue ADD COLUMN IF NOT EXISTS enqueued_by VARCHAR(255) DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN task_queue.enqueued_by IS '入队实例的 hostname(platform.node()),用于多实例任务隔离';
|
||||
|
||||
-- 回滚:
|
||||
-- ALTER TABLE task_queue DROP COLUMN IF EXISTS enqueued_by;
|
||||
@@ -0,0 +1,82 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_retention_clue.category CHECK 约束枚举对齐
|
||||
-- 日期:2026-03-08
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 将 category CHECK 约束中 '客户基础信息' 改为 '客户基础',
|
||||
-- 与 AI 应用 Prompt 枚举值统一(P5 spec 2026-03-08 评审决定)。
|
||||
-- '促销偏好' 保持不变(原 Prompt 中的 '促销接受' 已在 spec 侧对齐为 '促销偏好')。
|
||||
-- 统一后 6 个枚举值:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈
|
||||
-- 影响:
|
||||
-- - 已有数据中 category='客户基础信息' 的行需更新为 '客户基础'
|
||||
-- - 后端 API:POST /api/retention-clue 的 category 校验需同步
|
||||
-- - AI 应用 3/6/8 的 Prompt 枚举已在 P5 spec 中统一
|
||||
-- 幂等:先检查约束是否存在再操作,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 更新已有数据(先于约束变更)
|
||||
-- ============================================================
|
||||
UPDATE member_retention_clue
|
||||
SET category = '客户基础'
|
||||
WHERE category = '客户基础信息';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 删除旧 CHECK 约束
|
||||
-- ============================================================
|
||||
ALTER TABLE member_retention_clue
|
||||
DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建新 CHECK 约束('客户基础信息' → '客户基础')
|
||||
-- ============================================================
|
||||
ALTER TABLE member_retention_clue
|
||||
ADD CONSTRAINT chk_retention_clue_category CHECK (
|
||||
category IN ('客户基础', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. 更新列注释
|
||||
-- ============================================================
|
||||
COMMENT ON COLUMN member_retention_clue.category
|
||||
IS '线索大类枚举:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- UPDATE member_retention_clue SET category = '客户基础信息' WHERE category = '客户基础';
|
||||
-- ALTER TABLE member_retention_clue DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
|
||||
-- ALTER TABLE member_retention_clue ADD CONSTRAINT chk_retention_clue_category CHECK (
|
||||
-- category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
-- );
|
||||
-- COMMENT ON COLUMN member_retention_clue.category
|
||||
-- IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认无 '客户基础信息' 残留
|
||||
-- SELECT COUNT(*) FROM member_retention_clue WHERE category = '客户基础信息';
|
||||
-- 预期:0
|
||||
|
||||
-- 2. 确认 CHECK 约束内容
|
||||
-- SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
|
||||
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
|
||||
-- 预期:chk_retention_clue_category,包含 '客户基础'(非 '客户基础信息')
|
||||
|
||||
-- 3. 测试新枚举值可写入
|
||||
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
|
||||
-- VALUES (0, '客户基础', '测试', 0);
|
||||
-- 预期:成功
|
||||
-- DELETE FROM member_retention_clue WHERE member_id = 0 AND summary = '测试';
|
||||
|
||||
-- 4. 测试旧枚举值被拒绝
|
||||
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
|
||||
-- VALUES (0, '客户基础信息', '测试', 0);
|
||||
-- 预期:CHECK 约束违反错误
|
||||
65
db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql
Normal file
65
db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- ============================================================
|
||||
-- P5 AI 集成层:创建 AI 对话、消息、缓存三张表
|
||||
-- 需求: 1.1, 1.2, 1.3, 1.4, 1.5
|
||||
-- ============================================================
|
||||
|
||||
-- 1. biz.ai_conversations —— AI 对话记录
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_conversations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
nickname VARCHAR(100) NOT NULL DEFAULT '',
|
||||
app_id VARCHAR(30) NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
source_page VARCHAR(100),
|
||||
source_context JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_conversations IS 'AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条';
|
||||
COMMENT ON COLUMN biz.ai_conversations.app_id IS '应用标识:app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation';
|
||||
COMMENT ON COLUMN biz.ai_conversations.user_id IS '用户 ID,系统自动调用时为 system';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_conv_user_site ON biz.ai_conversations (user_id, site_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_conv_app_site ON biz.ai_conversations (app_id, site_id, created_at DESC);
|
||||
|
||||
-- 2. biz.ai_messages —— AI 消息记录
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL REFERENCES biz.ai_conversations(id) ON DELETE CASCADE,
|
||||
role VARCHAR(10) NOT NULL
|
||||
CONSTRAINT chk_ai_msg_role CHECK (role IN ('user', 'assistant', 'system')),
|
||||
content TEXT NOT NULL,
|
||||
tokens_used INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_messages IS 'AI 消息记录:对话中的每条消息(输入/输出/系统)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_msg_conv ON biz.ai_messages (conversation_id, created_at);
|
||||
|
||||
-- 3. biz.ai_cache —— AI 应用缓存
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_cache (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
cache_type VARCHAR(30) NOT NULL
|
||||
CONSTRAINT chk_ai_cache_type CHECK (
|
||||
cache_type IN (
|
||||
'app2_finance', 'app3_clue', 'app4_analysis',
|
||||
'app5_tactics', 'app6_note_analysis',
|
||||
'app7_customer_analysis', 'app8_clue_consolidated'
|
||||
)
|
||||
),
|
||||
site_id BIGINT NOT NULL,
|
||||
target_id VARCHAR(100) NOT NULL,
|
||||
result_json JSONB NOT NULL,
|
||||
score INTEGER,
|
||||
triggered_by VARCHAR(100),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_cache IS 'AI 应用缓存:各应用的结构化输出结果';
|
||||
COMMENT ON COLUMN biz.ai_cache.target_id IS '目标 ID:App2=时间维度编码 / App3,6,7,8=member_id / App4,5={assistant_id}_{member_id}';
|
||||
COMMENT ON COLUMN biz.ai_cache.score IS '评分:仅应用 6 使用(1-10 分)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_cache_lookup ON biz.ai_cache (cache_type, site_id, target_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_cache_cleanup ON biz.ai_cache (cache_type, site_id, target_id, created_at);
|
||||
@@ -0,0 +1,13 @@
|
||||
-- 给 task_execution_log 添加 schedule_id 字段,关联调度任务
|
||||
-- 用于查询某个调度任务的执行历史
|
||||
ALTER TABLE task_execution_log
|
||||
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;
|
||||
|
||||
-- 按 schedule_id 查询执行历史的部分索引
|
||||
CREATE INDEX IF NOT EXISTS idx_execution_log_schedule_id
|
||||
ON task_execution_log(schedule_id)
|
||||
WHERE schedule_id IS NOT NULL;
|
||||
|
||||
-- 给 task_queue 添加 schedule_id 字段,追溯调度来源
|
||||
ALTER TABLE task_queue
|
||||
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 触发器:INSERT task_execution_log 时自动从 task_queue 回填 schedule_id
|
||||
-- 背景:队列处理循环中 schedule_id 可能未通过代码传递,用触发器兜底
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_backfill_schedule_id()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.schedule_id IS NULL AND NEW.queue_id IS NOT NULL THEN
|
||||
SELECT schedule_id INTO NEW.schedule_id
|
||||
FROM task_queue
|
||||
WHERE id = NEW.queue_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_backfill_schedule_id ON task_execution_log;
|
||||
|
||||
CREATE TRIGGER trg_backfill_schedule_id
|
||||
BEFORE INSERT ON task_execution_log
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_backfill_schedule_id();
|
||||
|
||||
-- 回填历史数据
|
||||
UPDATE task_execution_log el
|
||||
SET schedule_id = tq.schedule_id
|
||||
FROM task_queue tq
|
||||
WHERE el.queue_id = tq.id
|
||||
AND tq.schedule_id IS NOT NULL
|
||||
AND el.schedule_id IS NULL;
|
||||
Reference in New Issue
Block a user