init: 项目初始提交 - NeoZQYY Monorepo 完整代码
This commit is contained in:
78
db/etl_feiqiu/README.md
Normal file
78
db/etl_feiqiu/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# database/ — 数据库层
|
||||
|
||||
## 文件说明
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `connection.py` | 数据库连接管理(带超时的 psycopg2 封装) |
|
||||
| `operations.py` | 批量操作(upsert、execute、query) |
|
||||
| `base.py` | 数据库操作基础类 |
|
||||
|
||||
## DDL Schema 文件
|
||||
|
||||
| 文件 | Schema | 说明 |
|
||||
|------|--------|------|
|
||||
| `schema_ODS_doc.sql` | `billiards_ods` | ODS 层表结构(含字段注释) |
|
||||
| `schema_dwd_doc.sql` | `billiards_dwd` | DWD 层表结构(维度 + 事实,含 SCD2 列) |
|
||||
| `schema_dws.sql` | `billiards_dws` | DWS 层表结构(汇总表 + 配置表) |
|
||||
| `schema_etl_admin.sql` | `etl_admin` | ETL 元数据(任务注册、游标、运行记录) |
|
||||
| `schema_verify_perf_indexes.sql` | 各 Schema | 校验性能索引(仅索引 + ANALYZE) |
|
||||
|
||||
## 种子脚本
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `seed_ods_tasks.sql` | 注册 ODS 任务到 `etl_admin.etl_task` |
|
||||
| `seed_scheduler_tasks.sql` | 初始化调度任务配置 |
|
||||
| `seed_dws_config.sql` | DWS 配置数据(绩效档位、等级定价、技能映射等) |
|
||||
| `seed_index_parameters.sql` | 指数算法参数(WBI/NCI/RS/OS/MS/ML) |
|
||||
|
||||
## 迁移脚本
|
||||
|
||||
位于 `migrations/` 子目录,纯 SQL,按日期前缀命名:
|
||||
|
||||
```
|
||||
migrations/
|
||||
├── 20260208_relation_index_manual_ml.sql
|
||||
├── 20260214_drop_ods_settlelist.sql
|
||||
├── 20260214_drop_dwd_settle_list.sql
|
||||
└── 20260214_drop_ods_option_name_able_site_transfer.sql
|
||||
```
|
||||
|
||||
新增迁移时,文件名格式:`YYYYMMDD_描述.sql`
|
||||
|
||||
## Schema 约定
|
||||
|
||||
- 所有 DDL 使用 `CREATE TABLE IF NOT EXISTS`,支持幂等执行
|
||||
- 表名小写蛇形,带 Schema 前缀(如 `billiards_dwd.dim_member`)
|
||||
- 维度表包含 SCD2 列:`scd2_start_time`、`scd2_end_time`、`scd2_is_current`、`scd2_version`
|
||||
- ODS 表包含元数据列:`content_hash`、`payload`、`fetched_at`、`source_file`
|
||||
- 金额字段统一 `NUMERIC(12,2)`,ID 字段统一 `BIGINT`
|
||||
- 不使用 ORM,所有 SQL 通过 `psycopg2` 直接执行
|
||||
|
||||
<!--
|
||||
AI_CHANGELOG:
|
||||
- 日期: 2026-02-14
|
||||
- Prompt: P20260214-030000 — 上下文传递续接,完成 settlelist 删除后的变更影响审查
|
||||
- 直接原因: Change Impact Review 要求将新增迁移脚本同步到 database/README.md
|
||||
- 变更摘要: 迁移脚本列表新增 20260214_drop_ods_settlelist.sql 条目
|
||||
- 风险与验证: 纯文档变更,无运行时影响;验证方式:`ls database/migrations/` 确认文件存在
|
||||
-->
|
||||
|
||||
<!--
|
||||
AI_CHANGELOG:
|
||||
- 日期: 2026-02-14
|
||||
- Prompt: P20260214-040000 — "dwd_settlement_head_ex.settle_list 也没有必要保留了"
|
||||
- 直接原因: 新增迁移脚本需同步到 README 迁移列表
|
||||
- 变更摘要: 迁移脚本列表新增 20260214_drop_dwd_settle_list.sql
|
||||
- 风险与验证: 纯文档变更;验证:ls database/migrations/ 确认文件存在
|
||||
-->
|
||||
|
||||
<!--
|
||||
AI_CHANGELOG:
|
||||
- 日期: 2026-02-14
|
||||
- Prompt: P20260214-070000 — ODS 清理(删除 option_name/able_site_transfer)变更影响审查
|
||||
- 直接原因: Change Impact Review 要求将新增迁移脚本同步到 database/README.md
|
||||
- 变更摘要: 迁移脚本列表新增 20260214_drop_ods_option_name_able_site_transfer.sql
|
||||
- 风险与验证: 纯文档变更;验证:ls database/migrations/ 确认文件存在
|
||||
-->
|
||||
0
db/etl_feiqiu/migrations/.gitkeep
Normal file
0
db/etl_feiqiu/migrations/.gitkeep
Normal file
144
db/etl_feiqiu/migrations/20260208_relation_index_manual_ml.sql
Normal file
144
db/etl_feiqiu/migrations/20260208_relation_index_manual_ml.sql
Normal 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;
|
||||
14
db/etl_feiqiu/migrations/20260213_align_ods_with_api.sql
Normal file
14
db/etl_feiqiu/migrations/20260213_align_ods_with_api.sql
Normal 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 等),
|
||||
-- 不需要将包装层字段作为列添加。
|
||||
--
|
||||
-- 无需执行此文件。
|
||||
72
db/etl_feiqiu/migrations/20260213_remove_legacy_index.sql
Normal file
72
db/etl_feiqiu/migrations/20260213_remove_legacy_index.sql
Normal 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 $;
|
||||
12
db/etl_feiqiu/migrations/20260214_drop_dwd_settle_list.sql
Normal file
12
db/etl_feiqiu/migrations/20260214_drop_dwd_settle_list.sql
Normal 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;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- 迁移:删除 ODS 层两个冗余列
|
||||
-- 日期:2026-02-14
|
||||
-- Prompt-ID:P20260214-070000
|
||||
-- 原因:option_name(store_goods_sales_records)和 able_site_transfer(member_stored_value_cards)
|
||||
-- 在 API JSON 响应中不存在,ODS 中全部为 NULL(0 条非空数据),属于冗余列
|
||||
-- 回滚: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 查询确认列不存在
|
||||
34
db/etl_feiqiu/migrations/20260214_drop_ods_settlelist.sql
Normal file
34
db/etl_feiqiu/migrations/20260214_drop_ods_settlelist.sql
Normal 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;
|
||||
0
db/etl_feiqiu/schemas/.gitkeep
Normal file
0
db/etl_feiqiu/schemas/.gitkeep
Normal file
214
db/etl_feiqiu/schemas/app.sql
Normal file
214
db/etl_feiqiu/schemas/app.sql
Normal file
@@ -0,0 +1,214 @@
|
||||
-- =============================================================================
|
||||
-- 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 — 助教日明细视图
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW app.v_assistant_daily AS
|
||||
SELECT
|
||||
d.id,
|
||||
d.site_id,
|
||||
d.assistant_id,
|
||||
d.stat_date,
|
||||
d.total_service_hours,
|
||||
d.total_service_count,
|
||||
d.total_revenue,
|
||||
d.basic_hours,
|
||||
d.extra_hours,
|
||||
d.tip_hours,
|
||||
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 — 财务日报视图
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW app.v_finance_daily AS
|
||||
SELECT
|
||||
f.id,
|
||||
f.site_id,
|
||||
f.stat_date,
|
||||
f.total_revenue,
|
||||
f.table_fee_revenue,
|
||||
f.goods_revenue,
|
||||
f.assistant_revenue,
|
||||
f.recharge_revenue,
|
||||
f.total_orders,
|
||||
f.total_customers,
|
||||
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 — 会员消费汇总视图
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW app.v_member_consumption AS
|
||||
SELECT
|
||||
mc.id,
|
||||
mc.site_id,
|
||||
mc.member_id,
|
||||
mc.total_visits,
|
||||
mc.total_consumption,
|
||||
mc.last_visit_date,
|
||||
mc.first_visit_date,
|
||||
mc.avg_consumption,
|
||||
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 — 订单汇总视图
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW app.v_order_summary AS
|
||||
SELECT
|
||||
os.site_id,
|
||||
os.order_settle_id,
|
||||
os.order_trade_no,
|
||||
os.member_id,
|
||||
os.total_amount,
|
||||
os.actual_amount,
|
||||
os.settle_time,
|
||||
os.pay_status,
|
||||
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;
|
||||
161
db/etl_feiqiu/schemas/core.sql
Normal file
161
db/etl_feiqiu/schemas/core.sql
Normal 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_site(14 字段 → 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_member(12 字段 → 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_assistant(16 字段 → 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_table(8 字段 → 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_category(10 字段 → 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);
|
||||
2089
db/etl_feiqiu/schemas/dwd.sql
Normal file
2089
db/etl_feiqiu/schemas/dwd.sql
Normal file
File diff suppressed because it is too large
Load Diff
1675
db/etl_feiqiu/schemas/dws.sql
Normal file
1675
db/etl_feiqiu/schemas/dws.sql
Normal file
File diff suppressed because it is too large
Load Diff
105
db/etl_feiqiu/schemas/meta.sql
Normal file
105
db/etl_feiqiu/schemas/meta.sql
Normal 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 '附加字段,保留扩展。';
|
||||
2057
db/etl_feiqiu/schemas/ods.sql
Normal file
2057
db/etl_feiqiu/schemas/ods.sql
Normal file
File diff suppressed because it is too large
Load Diff
2057
db/etl_feiqiu/schemas/schema_ODS_doc.sql
Normal file
2057
db/etl_feiqiu/schemas/schema_ODS_doc.sql
Normal file
File diff suppressed because it is too large
Load Diff
2089
db/etl_feiqiu/schemas/schema_dwd_doc.sql
Normal file
2089
db/etl_feiqiu/schemas/schema_dwd_doc.sql
Normal file
File diff suppressed because it is too large
Load Diff
1675
db/etl_feiqiu/schemas/schema_dws.sql
Normal file
1675
db/etl_feiqiu/schemas/schema_dws.sql
Normal file
File diff suppressed because it is too large
Load Diff
105
db/etl_feiqiu/schemas/schema_etl_admin.sql
Normal file
105
db/etl_feiqiu/schemas/schema_etl_admin.sql
Normal 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 '附加字段,保留扩展。';
|
||||
173
db/etl_feiqiu/schemas/schema_verify_perf_indexes.sql
Normal file
173
db/etl_feiqiu/schemas/schema_verify_perf_indexes.sql
Normal 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
|
||||
$$;
|
||||
|
||||
33
db/etl_feiqiu/scripts/create_test_db.sql
Normal file
33
db/etl_feiqiu/scripts/create_test_db.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- =============================================================================
|
||||
-- 创建测试数据库 test_etl_feiqiu
|
||||
-- 结构与生产 etl_feiqiu 完全一致(六层 schema:meta/ods/dwd/core/dws/app)
|
||||
-- 用途:开发和测试环境,避免影响生产数据
|
||||
-- =============================================================================
|
||||
|
||||
-- 1. 创建数据库(需要以超级用户或有 CREATEDB 权限的角色执行)
|
||||
-- 如果数据库已存在,先手动 DROP 或跳过此步
|
||||
CREATE DATABASE test_etl_feiqiu
|
||||
WITH ENCODING = 'UTF8'
|
||||
LC_COLLATE = 'zh_CN.UTF-8'
|
||||
LC_CTYPE = 'zh_CN.UTF-8'
|
||||
TEMPLATE = template0;
|
||||
|
||||
-- 2. 连接到 test_etl_feiqiu 后,依次执行以下脚本初始化六层 schema
|
||||
-- 每个脚本包含 CREATE SCHEMA IF NOT EXISTS + 该层所有表定义
|
||||
--
|
||||
-- 在 psql 中执行:
|
||||
-- \c test_etl_feiqiu
|
||||
-- \i ../schemas/meta.sql
|
||||
-- \i ../schemas/ods.sql
|
||||
-- \i ../schemas/dwd.sql
|
||||
-- \i ../schemas/core.sql
|
||||
-- \i ../schemas/dws.sql
|
||||
-- \i ../schemas/app.sql
|
||||
--
|
||||
-- 3. 如需加载种子数据,执行 seeds 目录下的脚本:
|
||||
-- \i ../seeds/*.sql
|
||||
|
||||
-- 注意事项:
|
||||
-- - 生产 schema 变更后,需同步在测试库执行相同的迁移脚本(Requirements 9.4)
|
||||
-- - 迁移脚本位于 ../migrations/ 目录,按日期前缀顺序执行
|
||||
-- - app schema 的 RLS 策略在测试库中同样生效,测试时需设置 app.current_site_id
|
||||
0
db/etl_feiqiu/seeds/.gitkeep
Normal file
0
db/etl_feiqiu/seeds/.gitkeep
Normal file
389
db/etl_feiqiu/seeds/seed_dws_config.sql
Normal file
389
db/etl_feiqiu/seeds/seed_dws_config.sql
Normal file
@@ -0,0 +1,389 @@
|
||||
-- =============================================================================
|
||||
-- DWS 配置表初始数据
|
||||
-- 版本: v3.0
|
||||
-- 创建日期: 2026-02-01
|
||||
-- 描述: 初始化配置表数据,包含绩效档位、等级定价、奖金规则、区域分类、技能映射
|
||||
-- =============================================================================
|
||||
|
||||
-- NOTE: 当前数据库 cfg_* 配置表为空(以数据库现状为准)
|
||||
-- 下方默认配置仅作参考,已整体注释
|
||||
/*
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. cfg_performance_tier - 绩效档位配置(含历史口径)
|
||||
-- 数据来源:DWS 数据库处理需求.md
|
||||
-- 旧方案(历史口径,至2026-02-28):
|
||||
-- 0档 淘汰压力 H <100 28 50% 3
|
||||
-- 1档 及格档(重点激励) 100≤ H <130 18 40% 4
|
||||
-- 2档 良好档(重点激励) 130≤ H <160 15 38% 4
|
||||
-- 3档 优秀档 160≤ H <190 13 35% 5
|
||||
-- 4档 卓越加速档(高端人才倾斜) 190≤ H <220 10 33% 6
|
||||
-- 5档 冠军加速档(高端人才倾斜) H ≥220 8 30% 休假自由
|
||||
-- 新方案(2026-03-01起):
|
||||
-- 0档 淘汰压力 H <120 28 50% 3
|
||||
-- 1档 及格档 120≤ H <150 18 40% 4
|
||||
-- 2档 良好档 150≤ H <180 13 35% 5
|
||||
-- 3档 优秀档 180≤ H <210 10 30% 6
|
||||
-- 4档 销冠竞争 H ≥210 8 25% 休假自由
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE billiards_dws.cfg_performance_tier RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO billiards_dws.cfg_performance_tier (
|
||||
tier_code, tier_name, tier_level,
|
||||
min_hours, max_hours,
|
||||
base_deduction, bonus_deduction_ratio, vacation_days, vacation_unlimited,
|
||||
is_new_hire_tier, effective_from, effective_to, description
|
||||
) VALUES
|
||||
-- 旧方案(至2026-02-28)
|
||||
-- 0档 淘汰压力: H<100, 专业课抽成28元/小时, 打赏课抽成50%, 休假3天
|
||||
('T0', '0档-淘汰压力', 0,
|
||||
0, 100,
|
||||
28.00, 0.50, 3, FALSE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:H<100,专业课抽成28元/小时,打赏课抽成50%,休假3天'),
|
||||
|
||||
-- 1档 及格档: 100≤H<130, 专业课抽成18元/小时, 打赏课抽成40%, 休假4天
|
||||
('T1', '1档-及格档', 1,
|
||||
100, 130,
|
||||
18.00, 0.40, 4, FALSE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:100≤H<130,专业课抽成18元/小时,打赏课抽成40%,休假4天'),
|
||||
|
||||
-- 2档 良好档: 130≤H<160, 专业课抽成15元/小时, 打赏课抽成38%, 休假4天
|
||||
('T2', '2档-良好档', 2,
|
||||
130, 160,
|
||||
15.00, 0.38, 4, FALSE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:130≤H<160,专业课抽成15元/小时,打赏课抽成38%,休假4天'),
|
||||
|
||||
-- 3档 优秀档: 160≤H<190, 专业课抽成13元/小时, 打赏课抽成35%, 休假5天
|
||||
('T3', '3档-优秀档', 3,
|
||||
160, 190,
|
||||
13.00, 0.35, 5, FALSE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:160≤H<190,专业课抽成13元/小时,打赏课抽成35%,休假5天'),
|
||||
|
||||
-- 4档 卓越加速档: 190≤H<220, 专业课抽成10元/小时, 打赏课抽成33%, 休假6天
|
||||
('T4', '4档-卓越加速档', 4,
|
||||
190, 220,
|
||||
10.00, 0.33, 6, FALSE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:190≤H<220,专业课抽成10元/小时,打赏课抽成33%,休假6天'),
|
||||
|
||||
-- 5档 冠军加速档: H≥220, 专业课抽成8元/小时, 打赏课抽成30%, 休假自由
|
||||
('T5', '5档-冠军加速档', 5,
|
||||
220, NULL,
|
||||
8.00, 0.30, 0, TRUE,
|
||||
FALSE, '2000-01-01', '2026-02-28',
|
||||
'旧方案:H≥220,专业课抽成8元/小时,打赏课抽成30%,休假自由'),
|
||||
|
||||
-- 新方案(2026-03-01起)
|
||||
-- 0档 淘汰压力: H<120, 专业课抽成28元/小时, 打赏课抽成50%, 休假3天
|
||||
('T0', '0档-淘汰压力', 0,
|
||||
0, 120,
|
||||
28.00, 0.50, 3, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:H<120,专业课抽成28元/小时,打赏课抽成50%,休假3天'),
|
||||
|
||||
-- 1档 及格档: 120≤H<150, 专业课抽成18元/小时, 打赏课抽成40%, 休假4天
|
||||
('T1', '1档-及格档', 1,
|
||||
120, 150,
|
||||
18.00, 0.40, 4, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:120≤H<150,专业课抽成18元/小时,打赏课抽成40%,休假4天'),
|
||||
|
||||
-- 2档 良好档: 150≤H<180, 专业课抽成13元/小时, 打赏课抽成35%, 休假5天
|
||||
('T2', '2档-良好档', 2,
|
||||
150, 180,
|
||||
13.00, 0.35, 5, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:150≤H<180,专业课抽成13元/小时,打赏课抽成35%,休假5天'),
|
||||
|
||||
-- 3档 优秀档: 180≤H<210, 专业课抽成10元/小时, 打赏课抽成30%, 休假6天
|
||||
('T3', '3档-优秀档', 3,
|
||||
180, 210,
|
||||
10.00, 0.30, 6, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:180≤H<210,专业课抽成10元/小时,打赏课抽成30%,休假6天'),
|
||||
|
||||
-- 4档 销冠竞争: H≥210, 专业课抽成8元/小时, 打赏课抽成25%, 休假自由
|
||||
('T4', '4档-销冠竞争', 4,
|
||||
210, NULL,
|
||||
8.00, 0.25, 0, TRUE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:H≥210,专业课抽成8元/小时,打赏课抽成25%,休假自由');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. cfg_assistant_level_price - 助教等级定价
|
||||
-- 说明:
|
||||
-- - level_code 来自 dim_assistant.assistant_level
|
||||
-- - 8=助教管理, 10=初级, 20=中级, 30=高级, 40=星级
|
||||
-- - 价格为客户支付价格(对外价格),助教收入=客户支付-档位抽成
|
||||
-- - 包厢课基础课统一138元/小时(不随等级变化)
|
||||
-- - 数据来源:DWS 数据库处理需求.md
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE billiards_dws.cfg_assistant_level_price RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO billiards_dws.cfg_assistant_level_price (
|
||||
level_code, level_name,
|
||||
base_course_price, bonus_course_price,
|
||||
effective_from, effective_to, description
|
||||
) VALUES
|
||||
-- 初级助教:基础课对客户收费98元/小时
|
||||
(10, '初级',
|
||||
98.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'初级助教:基础课98元/时,附加课190元/时(客户支付价格)'),
|
||||
|
||||
-- 中级助教:基础课对客户收费108元/小时
|
||||
(20, '中级',
|
||||
108.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'中级助教:基础课108元/时,附加课190元/时(客户支付价格)'),
|
||||
|
||||
-- 高级助教:基础课对客户收费118元/小时
|
||||
(30, '高级',
|
||||
118.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'高级助教:基础课118元/时,附加课190元/时(客户支付价格)'),
|
||||
|
||||
-- 星级助教:基础课对客户收费138元/小时
|
||||
(40, '星级',
|
||||
138.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'星级助教:基础课138元/时,附加课190元/时(客户支付价格)'),
|
||||
|
||||
-- 助教管理:level_code=8,通常不参与客户服务计费,此处设置默认值
|
||||
(8, '助教管理',
|
||||
98.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'助教管理:不参与客户服务计费,默认按初级价格');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. cfg_bonus_rules - 奖金规则配置
|
||||
-- 说明:
|
||||
-- - SPRINT: 冲刺奖金(历史口径,至2026-02-28)
|
||||
-- - TOP_RANK: Top3排名奖金(2026-03-01起)
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE billiards_dws.cfg_bonus_rules RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO billiards_dws.cfg_bonus_rules (
|
||||
rule_type, rule_code, rule_name,
|
||||
threshold_hours, rank_position, bonus_amount,
|
||||
is_cumulative, priority,
|
||||
effective_from, effective_to, description
|
||||
) VALUES
|
||||
-- 冲刺奖金: H>=190 得300元(历史口径)
|
||||
('SPRINT', 'SPRINT_190', '冲刺奖金190',
|
||||
190.00, NULL, 300.00,
|
||||
FALSE, 1,
|
||||
'2000-01-01', '2026-02-28',
|
||||
'历史口径:业绩≥190小时,获得300元冲刺奖金(不累计)'),
|
||||
|
||||
-- 冲刺奖金: H>=220 得800元(历史口径,优先级更高,覆盖190档)
|
||||
('SPRINT', 'SPRINT_220', '冲刺奖金220',
|
||||
220.00, NULL, 800.00,
|
||||
FALSE, 2,
|
||||
'2000-01-01', '2026-02-28',
|
||||
'历史口径:业绩≥220小时,获得800元冲刺奖金(覆盖190档)'),
|
||||
|
||||
-- Top1排名奖金: 1000元(2026-03-01起)
|
||||
('TOP_RANK', 'TOP_1', 'Top1排名奖金',
|
||||
NULL, 1, 1000.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第一,获得1000元(并列都算)'),
|
||||
|
||||
-- Top2排名奖金: 600元(2026-03-01起)
|
||||
('TOP_RANK', 'TOP_2', 'Top2排名奖金',
|
||||
NULL, 2, 600.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第二,获得600元(并列都算)'),
|
||||
|
||||
-- Top3排名奖金: 400元(2026-03-01起)
|
||||
('TOP_RANK', 'TOP_3', 'Top3排名奖金',
|
||||
NULL, 3, 400.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第三,获得400元(并列都算)');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. cfg_area_category - 台区分类映射
|
||||
-- 说明:
|
||||
-- - 将 dim_table.site_table_area_name 映射到财务报表区域分类
|
||||
-- - 映射规则: 精确匹配 > 模糊匹配 > 默认兜底
|
||||
-- - 数据来源: BD_manual_dim_table.md 中的 site_table_area_name 实际分布
|
||||
-- 分类设计:
|
||||
-- - BILLIARD: 台球散台(A区/B区/C区/TV台)
|
||||
-- - BILLIARD_VIP: 台球VIP包厢
|
||||
-- - SNOOKER: 斯诺克区
|
||||
-- - MAHJONG: 麻将区
|
||||
-- - KTV: K歌/KTV
|
||||
-- - SPECIAL: 特殊(补时长等)
|
||||
-- - OTHER: 其他
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE billiards_dws.cfg_area_category RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO billiards_dws.cfg_area_category (
|
||||
source_area_name, category_code, category_name,
|
||||
match_type, match_priority, is_active, description
|
||||
) VALUES
|
||||
-- ============ 台球散台区(精确匹配)============
|
||||
('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斯诺克'),
|
||||
|
||||
-- ============ 斯诺克区(精确匹配)============
|
||||
('斯诺克区', '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台)'),
|
||||
|
||||
-- ============ 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台)'),
|
||||
|
||||
-- ============ 特殊区域(精确匹配)============
|
||||
('补时长', '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"的区域'),
|
||||
|
||||
-- ============ 默认兜底(优先级最低)============
|
||||
('DEFAULT', 'OTHER', '其他',
|
||||
'DEFAULT', 999, TRUE, '兜底规则:无法匹配的区域归入其他');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 5. cfg_skill_type - 技能→课程类型映射
|
||||
-- 说明:
|
||||
-- - 将 skill_id 映射到课程类型
|
||||
-- - 基础课/陪打: skill_id = 2791903611396869
|
||||
-- - 附加课/超休: skill_id = 2807440316432197
|
||||
-- - 避免依赖 skill_name 文本匹配
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE billiards_dws.cfg_skill_type RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO billiards_dws.cfg_skill_type (
|
||||
skill_id, skill_name,
|
||||
course_type_code, course_type_name,
|
||||
is_active, description
|
||||
) VALUES
|
||||
-- 基础课/陪打
|
||||
(2791903611396869, '台球基础陪打',
|
||||
'BASE', '基础课',
|
||||
TRUE, '基础课:陪打服务,按助教等级计价'),
|
||||
|
||||
-- 附加课/超休
|
||||
(2807440316432197, '台球超休服务',
|
||||
'BONUS', '附加课',
|
||||
TRUE, '附加课:超休/激励课,固定190元/小时'),
|
||||
|
||||
-- 包厢课(如有)
|
||||
(2807440316432198, '包厢服务',
|
||||
'BASE', '基础课',
|
||||
TRUE, '包厢服务:归入基础课统计,统一按138元/小时计价');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 6. 优惠类型配置(用于财务优惠明细分析)
|
||||
-- 说明: 定义各类优惠的代码和名称,便于后续分析
|
||||
-- =============================================================================
|
||||
-- 此配置作为代码常量使用,不单独建表
|
||||
-- GROUPBUY - 团购优惠
|
||||
-- VIP - 会员折扣
|
||||
-- GIFT_CARD - 赠送卡抵扣
|
||||
-- MANUAL - 手动调整
|
||||
-- ROUNDING - 抹零
|
||||
-- BIG_CUSTOMER - 大客户优惠(待抽样分析确认)
|
||||
-- OTHER - 其他优惠
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 7. 支出类型配置(用于Excel导入)
|
||||
-- 说明: 定义各类支出的代码和名称
|
||||
-- =============================================================================
|
||||
-- 此配置作为代码常量使用,不单独建表
|
||||
-- RENT - 房租
|
||||
-- UTILITY - 水电费
|
||||
-- PROPERTY - 物业费
|
||||
-- SALARY - 工资
|
||||
-- REIMBURSE - 报销
|
||||
-- PLATFORM_FEE - 平台服务费
|
||||
-- OTHER - 其他支出
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 8. 平台类型配置(用于Excel导入)
|
||||
-- 说明: 定义各平台的代码和名称
|
||||
-- =============================================================================
|
||||
-- 此配置作为代码常量使用,不单独建表
|
||||
-- MEITUAN - 美团
|
||||
-- DOUYIN - 抖音
|
||||
-- DIANPING - 大众点评
|
||||
-- OTHER - 其他平台
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证数据插入
|
||||
-- =============================================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
v_tier_count INTEGER;
|
||||
v_price_count INTEGER;
|
||||
v_bonus_count INTEGER;
|
||||
v_area_count INTEGER;
|
||||
v_skill_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_tier_count FROM billiards_dws.cfg_performance_tier;
|
||||
SELECT COUNT(*) INTO v_price_count FROM billiards_dws.cfg_assistant_level_price;
|
||||
SELECT COUNT(*) INTO v_bonus_count FROM billiards_dws.cfg_bonus_rules;
|
||||
SELECT COUNT(*) INTO v_area_count FROM billiards_dws.cfg_area_category;
|
||||
SELECT COUNT(*) INTO v_skill_count FROM billiards_dws.cfg_skill_type;
|
||||
|
||||
RAISE NOTICE '配置数据初始化完成:';
|
||||
RAISE NOTICE ' - cfg_performance_tier: % 条', v_tier_count;
|
||||
RAISE NOTICE ' - cfg_assistant_level_price: % 条', v_price_count;
|
||||
RAISE NOTICE ' - cfg_bonus_rules: % 条', v_bonus_count;
|
||||
RAISE NOTICE ' - cfg_area_category: % 条', v_area_count;
|
||||
RAISE NOTICE ' - cfg_skill_type: % 条', v_skill_count;
|
||||
END;
|
||||
$$;
|
||||
*/
|
||||
190
db/etl_feiqiu/seeds/seed_index_parameters.sql
Normal file
190
db/etl_feiqiu/seeds/seed_index_parameters.sql
Normal file
@@ -0,0 +1,190 @@
|
||||
-- =============================================================================
|
||||
-- 指数算法参数初始化脚本
|
||||
-- 版本: v3.0
|
||||
-- 创建日期: 2026-02-13
|
||||
-- 描述: 仅保留 RS / OS / MS / ML / NCI / WBI 指数参数(已移除 RECALL / INTIMACY)
|
||||
-- AI_CHANGELOG [2026-02-13] 移除 RECALL/INTIMACY 参数及 ML 废弃参数(source_mode/recharge_attribute_hours)
|
||||
-- =============================================================================
|
||||
|
||||
-- 清理旧版指数参数
|
||||
DELETE FROM billiards_dws.cfg_index_parameters WHERE index_type IN ('RECALL', 'INTIMACY');
|
||||
-- 清理 ML 已废弃参数
|
||||
DELETE FROM billiards_dws.cfg_index_parameters WHERE index_type = 'ML' AND param_name IN ('source_mode', 'recharge_attribute_hours');
|
||||
|
||||
INSERT INTO billiards_dws.cfg_index_parameters
|
||||
(index_type, param_name, param_value, description, effective_from)
|
||||
VALUES
|
||||
('NCI', 'active_new_penalty', 0.200000, 'active-new suppression multiplier', DATE '2026-02-06'),
|
||||
('NCI', 'active_new_recency_days', 7.000000, 'active-new recency window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'active_new_visit_threshold_14d', 2.000000, 'active-new threshold in 14d visits', DATE '2026-02-06'),
|
||||
('NCI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
|
||||
('NCI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
|
||||
('NCI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
|
||||
('NCI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
|
||||
('NCI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
|
||||
('NCI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
|
||||
('NCI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
|
||||
('NCI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
|
||||
('NCI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
|
||||
('NCI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
|
||||
('NCI', 'no_touch_days_new', 3.000000, 'no-touch threshold (days)', DATE '2026-02-06'),
|
||||
('NCI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
|
||||
('NCI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
|
||||
('NCI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'salvage_end', 60.000000, 'salvage decay end day', DATE '2026-02-06'),
|
||||
('NCI', 'salvage_start', 30.000000, 'salvage decay start day', DATE '2026-02-06'),
|
||||
('NCI', 't2_target_days', 7.000000, 'second-visit target window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
|
||||
('NCI', 'value_w_bal', 0.800000, 'value weight for balance', DATE '2026-02-06'),
|
||||
('NCI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
|
||||
('NCI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
|
||||
('NCI', 'w_need', 1.600000, 'need weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_re', 0.800000, 'recharge pressure weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_value', 1.000000, 'value weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_welcome', 1.000000, 'welcome-stage weight', DATE '2026-02-06'),
|
||||
('NCI', 'welcome_window_days', 3.000000, 'welcome outreach window for first touch (days)', DATE '2026-02-06'),
|
||||
('WBI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
|
||||
('WBI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
|
||||
('WBI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
|
||||
('WBI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
|
||||
('WBI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
|
||||
('WBI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
|
||||
('WBI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
|
||||
('WBI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
|
||||
('WBI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
|
||||
('WBI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
|
||||
('WBI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
|
||||
('WBI', 'overdue_alpha', 2.000000, 'overdue fallback alpha', DATE '2026-02-06'),
|
||||
('WBI', 'overdue_weight_blend_min_samples', 8.000000, 'minimum samples to fully trust weighted overdue CDF', DATE '2026-02-07'),
|
||||
('WBI', 'overdue_weight_halflife_days', 30.000000, 'overdue weighted-CDF interval half-life (days)', DATE '2026-02-07'),
|
||||
('WBI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
|
||||
('WBI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
|
||||
('WBI', 'recency_gate_days', 14.000000, 'recency suppression gate center (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recency_gate_slope_days', 3.000000, 'recency suppression slope (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recency_hard_floor_days', 14.000000, 'hard floor for winback recency (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
|
||||
('WBI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
|
||||
('WBI', 'value_w_bal', 1.000000, 'value weight for balance', DATE '2026-02-06'),
|
||||
('WBI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
|
||||
('WBI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
|
||||
('WBI', 'w_drop', 1.000000, 'drop weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_over', 2.000000, 'overdue weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_re', 0.400000, 'recharge pressure weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_value', 1.200000, 'value weight', DATE '2026-02-06')
|
||||
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
|
||||
param_value = EXCLUDED.param_value,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = NOW();
|
||||
|
||||
-- =============================================================================
|
||||
-- 关系指数(RS/OS/MS/ML)参数
|
||||
-- 生效时间:北京时间 2026-01-01(按数据库日期管理)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO billiards_dws.cfg_index_parameters
|
||||
(index_type, param_name, param_value, description, effective_from)
|
||||
VALUES
|
||||
-- RS(关系强度)
|
||||
('RS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('RS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
|
||||
('RS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
|
||||
('RS', 'halflife_session', 14.000000, '会话半衰期(天)', DATE '2026-01-01'),
|
||||
('RS', 'halflife_last', 10.000000, '最近一次服务半衰期(天)', DATE '2026-01-01'),
|
||||
('RS', 'weight_f', 1.000000, '频次项权重', DATE '2026-01-01'),
|
||||
('RS', 'weight_d', 0.700000, '时长项权重', DATE '2026-01-01'),
|
||||
('RS', 'gate_alpha', 0.600000, '最近服务门控指数', DATE '2026-01-01'),
|
||||
('RS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('RS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('RS', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('RS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('RS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
|
||||
|
||||
-- OS(归属份额)
|
||||
('OS', 'min_rs_raw_for_ownership', 0.050000, '参与归属计算的最小RS_raw', DATE '2026-01-01'),
|
||||
('OS', 'min_total_rs_raw', 0.100000, '形成稳定归属的最小sum_rs', DATE '2026-01-01'),
|
||||
('OS', 'ownership_main_threshold', 0.600000, '主责阈值', DATE '2026-01-01'),
|
||||
('OS', 'ownership_comanage_threshold', 0.350000, '共管阈值', DATE '2026-01-01'),
|
||||
('OS', 'ownership_gap_threshold', 0.150000, '主责与次席份额差阈值', DATE '2026-01-01'),
|
||||
('OS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
|
||||
|
||||
-- MS(升温动量)
|
||||
('MS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('MS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
|
||||
('MS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
|
||||
('MS', 'halflife_short', 7.000000, '短期半衰期(天)', DATE '2026-01-01'),
|
||||
('MS', 'halflife_long', 30.000000, '长期半衰期(天)', DATE '2026-01-01'),
|
||||
('MS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
|
||||
('MS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('MS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('MS', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('MS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('MS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
|
||||
|
||||
-- ML(付费关联)
|
||||
('ML', 'lookback_days', 60.000000, '充值行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('ML', 'amount_base', 500.000000, '金额压缩基准', DATE '2026-01-01'),
|
||||
('ML', 'halflife_recharge', 21.000000, '充值半衰期(天)', DATE '2026-01-01'),
|
||||
('ML', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('ML', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('ML', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('ML', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('ML', 'ewma_alpha', 0.200000, 'EWMA平滑系数', 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();
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证
|
||||
-- =============================================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
rs_count INTEGER;
|
||||
os_count INTEGER;
|
||||
ms_count INTEGER;
|
||||
ml_count INTEGER;
|
||||
nci_count INTEGER;
|
||||
wbi_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO rs_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'RS';
|
||||
|
||||
SELECT COUNT(*) INTO os_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'OS';
|
||||
|
||||
SELECT COUNT(*) INTO ms_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'MS';
|
||||
|
||||
SELECT COUNT(*) INTO ml_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'ML';
|
||||
|
||||
SELECT COUNT(*) INTO nci_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'NCI';
|
||||
|
||||
SELECT COUNT(*) INTO wbi_count
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
WHERE index_type = 'WBI';
|
||||
|
||||
RAISE NOTICE 'RS 参数数量: %', rs_count;
|
||||
RAISE NOTICE 'OS 参数数量: %', os_count;
|
||||
RAISE NOTICE 'MS 参数数量: %', ms_count;
|
||||
RAISE NOTICE 'ML 参数数量: %', ml_count;
|
||||
RAISE NOTICE '新客转化参数数量: %', nci_count;
|
||||
RAISE NOTICE '唤回指数参数数量: %', wbi_count;
|
||||
END $$;
|
||||
|
||||
SELECT
|
||||
index_type,
|
||||
param_name,
|
||||
param_value,
|
||||
description,
|
||||
effective_from
|
||||
FROM billiards_dws.cfg_index_parameters
|
||||
ORDER BY index_type, param_name, effective_from;
|
||||
41
db/etl_feiqiu/seeds/seed_ods_tasks.sql
Normal file
41
db/etl_feiqiu/seeds/seed_ods_tasks.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
-- 将新的 ODS 任务注册到 etl_admin.etl_task(按需替换 store_id)。
|
||||
-- 使用方式(示例):
|
||||
-- psql "$PG_DSN" -f etl_billiards/database/seed_ods_tasks.sql
|
||||
-- 或在 psql 中直接执行本文件内容。
|
||||
|
||||
WITH target_store AS (
|
||||
SELECT 2790685415443269::bigint AS store_id -- TODO: 替换为实际 store_id
|
||||
),
|
||||
task_codes AS (
|
||||
SELECT unnest(ARRAY[
|
||||
-- Must match tasks/ods_tasks.py (ENABLED_ODS_CODES)
|
||||
'ODS_ASSISTANT_ACCOUNT',
|
||||
'ODS_ASSISTANT_LEDGER',
|
||||
'ODS_ASSISTANT_ABOLISH',
|
||||
'ODS_SETTLEMENT_RECORDS',
|
||||
'ODS_TABLE_USE',
|
||||
'ODS_PAYMENT',
|
||||
'ODS_REFUND',
|
||||
'ODS_PLATFORM_COUPON',
|
||||
'ODS_MEMBER',
|
||||
'ODS_MEMBER_CARD',
|
||||
'ODS_MEMBER_BALANCE',
|
||||
'ODS_RECHARGE_SETTLE',
|
||||
'ODS_GROUP_PACKAGE',
|
||||
'ODS_GROUP_BUY_REDEMPTION',
|
||||
'ODS_INVENTORY_STOCK',
|
||||
'ODS_INVENTORY_CHANGE',
|
||||
'ODS_TABLES',
|
||||
'ODS_GOODS_CATEGORY',
|
||||
'ODS_STORE_GOODS',
|
||||
'ODS_STORE_GOODS_SALES',
|
||||
'ODS_TABLE_FEE_DISCOUNT',
|
||||
'ODS_TENANT_GOODS',
|
||||
'ODS_SETTLEMENT_TICKET'
|
||||
]) AS task_code
|
||||
)
|
||||
INSERT INTO etl_admin.etl_task (task_code, store_id, enabled)
|
||||
SELECT t.task_code, s.store_id, TRUE
|
||||
FROM task_codes t CROSS JOIN target_store s
|
||||
ON CONFLICT (task_code, store_id) DO UPDATE
|
||||
SET enabled = EXCLUDED.enabled;
|
||||
54
db/etl_feiqiu/seeds/seed_scheduler_tasks.sql
Normal file
54
db/etl_feiqiu/seeds/seed_scheduler_tasks.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
-- Seed scheduler-compatible tasks into etl_admin.etl_task.
|
||||
-- AI_CHANGELOG [2026-02-13] 移除 DWS_RECALL_INDEX/DWS_INTIMACY_INDEX 任务种子
|
||||
--
|
||||
-- Notes:
|
||||
-- - These task_code values must match orchestration/task_registry.py.
|
||||
-- - ODS_* tasks are intentionally excluded here because they don't follow the
|
||||
-- BaseTask(cursor_data) scheduler interface in this repo version.
|
||||
--
|
||||
-- Usage (example):
|
||||
-- psql "%PG_DSN%" -f etl_billiards/database/seed_scheduler_tasks.sql
|
||||
--
|
||||
WITH target_store AS (
|
||||
SELECT 2790685415443269::bigint AS store_id -- TODO: replace with your store_id
|
||||
),
|
||||
task_codes AS (
|
||||
SELECT unnest(ARRAY[
|
||||
'ASSISTANT_ABOLISH',
|
||||
'ASSISTANTS',
|
||||
'COUPON_USAGE',
|
||||
'CHECK_CUTOFF',
|
||||
'DWD_LOAD_FROM_ODS',
|
||||
'DWD_QUALITY_CHECK',
|
||||
'INIT_DWD_SCHEMA',
|
||||
'INIT_DWS_SCHEMA',
|
||||
'INIT_ODS_SCHEMA',
|
||||
'INVENTORY_CHANGE',
|
||||
'LEDGER',
|
||||
'MANUAL_INGEST',
|
||||
'MEMBERS',
|
||||
'MEMBERS_DWD',
|
||||
'ODS_JSON_ARCHIVE',
|
||||
'ORDERS',
|
||||
'PACKAGES_DEF',
|
||||
'PAYMENTS',
|
||||
'PAYMENTS_DWD',
|
||||
'PRODUCTS',
|
||||
'REFUNDS',
|
||||
'TABLE_DISCOUNT',
|
||||
'TABLES',
|
||||
'TICKET_DWD',
|
||||
'TOPUPS',
|
||||
'DWS_BUILD_ORDER_SUMMARY',
|
||||
'DWS_WINBACK_INDEX',
|
||||
'DWS_NEWCONV_INDEX',
|
||||
'DWS_RELATION_INDEX',
|
||||
'DWS_ML_MANUAL_IMPORT'
|
||||
]) AS task_code
|
||||
)
|
||||
INSERT INTO etl_admin.etl_task (task_code, store_id, enabled)
|
||||
SELECT t.task_code, s.store_id, TRUE
|
||||
FROM task_codes t CROSS JOIN target_store s
|
||||
ON CONFLICT (task_code, store_id) DO UPDATE
|
||||
SET enabled = EXCLUDED.enabled,
|
||||
updated_at = now();
|
||||
Reference in New Issue
Block a user