This commit is contained in:
Neo
2026-03-15 10:15:02 +08:00
parent 2dd217522c
commit 72bb11b34f
916 changed files with 65306 additions and 16102803 deletions

View File

@@ -1,53 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 auth/biz Schema 与 app_user 权限配置
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在业务库中创建 auth用户认证和 biz业务数据两个 Schema
-- 并授予 app_user 角色完整的 CRUD 权限(含未来新表自动授权)。
-- 不操作 public Schema保留其中现有系统管理表不受影响。
-- 前提app_user 角色已由 DBA 预创建
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 创建 auth Schema用户认证、权限、映射
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 2. 创建 biz Schema业务数据
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 3. 授予 app_user 对 auth Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA auth TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth TO app_user;
-- ---------------------------------------------------------------------------
-- 4. 授予 app_user 对 biz Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA biz TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz TO app_user;
-- ---------------------------------------------------------------------------
-- 5. 设置 ALTER DEFAULT PRIVILEGES未来新表自动授权
-- ---------------------------------------------------------------------------
ALTER DEFAULT PRIVILEGES IN SCHEMA auth
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA biz
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- ALTER DEFAULT PRIVILEGES IN SCHEMA biz REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
-- ALTER DEFAULT PRIVILEGES IN SCHEMA auth REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
-- REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz FROM app_user;
-- REVOKE USAGE ON SCHEMA biz FROM app_user;
-- REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth FROM app_user;
-- REVOKE USAGE ON SCHEMA auth FROM app_user;
-- DROP SCHEMA IF EXISTS biz CASCADE;
-- DROP SCHEMA IF EXISTS auth CASCADE;

View File

@@ -1,71 +0,0 @@
-- =============================================================================
-- 迁移脚本:配置 FDW 跨库映射ETL 库 → 业务库)
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:通过 postgres_fdw 将 ETL 库 app Schema 的 RLS 视图映射为业务库
-- fdw_etl Schema 的只读外部表,使后端无需直连 ETL 库即可读取汇总/维度数据。
-- 前提:
-- 1. ETL 库已部署 app Schema 及 RLS 视图2026-02-24__p1_create_app_schema_rls_views.sql
-- 2. ETL 库已创建 app_reader 只读角色
-- 3. 业务库已创建 app_user 角色
-- 需求3.1, 3.2, 3.3, 3.4, 3.7, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 安装 postgres_fdw 扩展
-- ---------------------------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
-- ---------------------------------------------------------------------------
-- 2. 创建外部服务器(指向 ETL 库)
-- host / dbname / port 使用占位符 '***',部署时按环境替换
-- 服务器名使用通用名(不含环境前缀),通过连接参数区分环境
-- ---------------------------------------------------------------------------
CREATE SERVER IF NOT EXISTS etl_feiqiu_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '***', dbname '***', port '***');
-- ---------------------------------------------------------------------------
-- 3. 创建用户映射(只读角色)
-- app_user = test_zqyy_app 侧的应用连接角色
-- app_reader = ETL 库侧的只读角色
-- 密码占位符 '***',部署时替换为真实凭据
-- ---------------------------------------------------------------------------
CREATE USER MAPPING IF NOT EXISTS FOR app_user
SERVER etl_feiqiu_server
OPTIONS (user 'app_reader', password '***');
-- ---------------------------------------------------------------------------
-- 4. 创建 fdw_etl Schema幂等处理先 DROP 再重建)
-- IMPORT FOREIGN SCHEMA 不是幂等的(外部表已存在会报错),
-- 因此采用 DROP CASCADE + 重建的方式确保可重复执行。
-- ---------------------------------------------------------------------------
DROP SCHEMA IF EXISTS fdw_etl CASCADE;
CREATE SCHEMA fdw_etl;
-- ---------------------------------------------------------------------------
-- 5. 批量导入 ETL 库 app Schema 的所有外部表到 fdw_etl
-- ---------------------------------------------------------------------------
IMPORT FOREIGN SCHEMA app
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
-- ---------------------------------------------------------------------------
-- 6. 授权:允许 app_user 访问 fdw_etl Schema 及其外部表
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA fdw_etl TO app_user;
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_etl TO app_user;
-- 未来新导入的外部表自动授权
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl GRANT SELECT ON TABLES TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl REVOKE SELECT ON TABLES FROM app_user;
-- REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_etl FROM app_user;
-- REVOKE USAGE ON SCHEMA fdw_etl FROM app_user;
-- DROP SCHEMA IF EXISTS fdw_etl CASCADE;
-- DROP USER MAPPING IF EXISTS FOR app_user SERVER etl_feiqiu_server;
-- DROP SERVER IF EXISTS etl_feiqiu_server CASCADE;
-- DROP EXTENSION IF EXISTS postgres_fdw;

View File

@@ -1,280 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 auth Schema 认证业务表
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 下创建用户认证系统所需的 8 张业务表:
-- users、user_applications、site_code_mapping、roles、permissions、
-- role_permissions、user_site_roles、user_assistant_binding
-- 包含字段定义、约束、索引、外键。使用 IF NOT EXISTS 幂等语法。
-- 前提auth Schema 已由 P1 迁移脚本创建
-- 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, 1.10
-- =============================================================================
-- 确保 auth Schema 存在(幂等)
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 1. users — 微信用户主表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.users (
id SERIAL PRIMARY KEY,
wx_openid VARCHAR(128) NOT NULL,
wx_union_id VARCHAR(128),
wx_avatar_url VARCHAR(512),
nickname VARCHAR(50),
phone VARCHAR(20),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- wx_openid 唯一约束(幂等:先检查再添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_users_wx_openid' AND conrelid = 'auth.users'::regclass
) THEN
ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_users_wx_openid ON auth.users (wx_openid);
-- ---------------------------------------------------------------------------
-- 2. site_code_mapping — 球房ID与门店映射表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.site_code_mapping (
id SERIAL PRIMARY KEY,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT NOT NULL,
site_name VARCHAR(100),
tenant_id INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- site_code 唯一约束格式2字母+3数字如 AB123
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_code' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
END IF;
END $$;
-- site_id 唯一约束
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_id' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_site_code_mapping_site_code ON auth.site_code_mapping (site_code);
-- ---------------------------------------------------------------------------
-- 3. roles — 角色定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.roles (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_roles_code' AND conrelid = 'auth.roles'::regclass
) THEN
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 4. permissions — 权限定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.permissions (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_permissions_code' AND conrelid = 'auth.permissions'::regclass
) THEN
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 5. role_permissions — 角色-权限关联表(联合主键)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.role_permissions (
role_id INT NOT NULL,
permission_id INT NOT NULL,
PRIMARY KEY (role_id, permission_id)
);
-- 外键(幂等添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_role_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_role_id
FOREIGN KEY (role_id) REFERENCES auth.roles (id) ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_permission_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_permission_id
FOREIGN KEY (permission_id) REFERENCES auth.permissions (id) ON DELETE CASCADE;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 6. user_applications — 用户入驻申请表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_applications (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT,
applied_role_text VARCHAR(100) NOT NULL,
employee_number VARCHAR(50),
phone VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
reviewer_id INT,
review_note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
reviewed_at TIMESTAMPTZ
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_applications_user_id' AND conrelid = 'auth.user_applications'::regclass
) THEN
ALTER TABLE auth.user_applications
ADD CONSTRAINT fk_user_applications_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_applications_user_id ON auth.user_applications (user_id);
CREATE INDEX IF NOT EXISTS ix_user_applications_status ON auth.user_applications (status);
-- ---------------------------------------------------------------------------
-- 7. user_site_roles — 用户-门店-角色关联表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_site_roles (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
role_id INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_user_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT fk_user_site_roles_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_role_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT fk_user_site_roles_role_id
FOREIGN KEY (role_id) REFERENCES auth.roles (id) ON DELETE CASCADE;
END IF;
END $$;
-- 唯一约束:同一用户在同一门店下不能重复分配同一角色
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_user_site_roles_user_site_role' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT uq_user_site_roles_user_site_role UNIQUE (user_id, site_id, role_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_site_roles_user_site ON auth.user_site_roles (user_id, site_id);
-- ---------------------------------------------------------------------------
-- 8. user_assistant_binding — 用户-人员绑定表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_assistant_binding (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
assistant_id BIGINT,
staff_id BIGINT,
binding_type VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_assistant_binding_user_id' AND conrelid = 'auth.user_assistant_binding'::regclass
) THEN
ALTER TABLE auth.user_assistant_binding
ADD CONSTRAINT fk_user_assistant_binding_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 授予 app_user 对新表的 SEQUENCE 使用权限SERIAL 字段需要)
-- ---------------------------------------------------------------------------
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA auth TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- DROP TABLE IF EXISTS auth.user_assistant_binding CASCADE;
-- DROP TABLE IF EXISTS auth.user_site_roles CASCADE;
-- DROP TABLE IF EXISTS auth.user_applications CASCADE;
-- DROP TABLE IF EXISTS auth.role_permissions CASCADE;
-- DROP TABLE IF EXISTS auth.permissions CASCADE;
-- DROP TABLE IF EXISTS auth.roles CASCADE;
-- DROP TABLE IF EXISTS auth.site_code_mapping CASCADE;
-- DROP TABLE IF EXISTS auth.users CASCADE;

View File

@@ -1,75 +0,0 @@
-- =============================================================================
-- 种子数据脚本:预置权限列表、默认角色、角色-权限映射
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 的 permissions、roles、role_permissions 表中插入种子数据。
-- 使用 ON CONFLICT DO NOTHING 幂等语法,重复执行不会产生重复数据。
-- 前提auth.roles、auth.permissions、auth.role_permissions 表已由
-- 2026-02-25__p3_create_auth_tables.sql 创建
-- 需求2.1, 2.2, 2.3, 2.4
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 插入固定权限5 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.permissions (code, name, description) VALUES
('view_tasks', '查看任务', '允许查看任务列表和任务详情'),
('view_board', '查看看板', '允许查看数据看板概览'),
('view_board_finance', '查看财务看板', '允许查看财务相关的数据看板'),
('view_board_customer', '查看客户看板', '允许查看客户相关的数据看板'),
('view_board_coach', '查看助教看板', '允许查看助教相关的数据看板')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 2. 插入默认角色4 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.roles (code, name, description) VALUES
('coach', '助教', '球房助教,可查看任务和助教看板'),
('staff', '员工', '球房员工,可查看任务和数据看板'),
('site_admin', '店铺管理员', '单店管理员,可查看所有看板'),
('tenant_admin', '租户管理员', '租户级管理员,拥有全部权限')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 3. 插入角色-权限映射
-- coach: view_tasks, view_board_coach
-- staff: view_tasks, view_board
-- site_admin: view_tasks, view_board, view_board_finance, view_board_customer, view_board_coach
-- tenant_admin: 全部 5 个权限
-- ---------------------------------------------------------------------------
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r
CROSS JOIN auth.permissions p
WHERE (r.code, p.code) IN (
-- coach: 2 个权限
('coach', 'view_tasks'),
('coach', 'view_board_coach'),
-- staff: 2 个权限
('staff', 'view_tasks'),
('staff', 'view_board'),
-- site_admin: 5 个权限
('site_admin', 'view_tasks'),
('site_admin', 'view_board'),
('site_admin', 'view_board_finance'),
('site_admin', 'view_board_customer'),
('site_admin', 'view_board_coach'),
-- tenant_admin: 5 个权限
('tenant_admin', 'view_tasks'),
('tenant_admin', 'view_board'),
('tenant_admin', 'view_board_finance'),
('tenant_admin', 'view_board_customer'),
('tenant_admin', 'view_board_coach')
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- DELETE FROM auth.role_permissions
-- WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin'))
-- AND permission_id IN (SELECT id FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach'));
--
-- DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
--
-- DELETE FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');

View File

@@ -1,99 +0,0 @@
-- =============================================================================
-- 迁移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 的逻辑
-- - FDWfdw_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 行主键索引

View File

@@ -1,21 +0,0 @@
-- 迁移:为 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';

View File

@@ -1,52 +0,0 @@
-- =============================================================================
-- 迁移member_retention_clue 新增 source 字段
-- 日期2026-02-27
-- 目标库zqyy_app / test_zqyy_app
-- 说明:
-- 区分维客线索来源manual助教手动/ ai_consumption应用3/ ai_note应用6
-- 影响:
-- - 后端 APIPOST /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'

View File

@@ -1,107 +0,0 @@
-- =============================================================================
-- 迁移脚本:创建 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;

View File

@@ -1,43 +0,0 @@
-- =============================================================================
-- 迁移脚本:预置触发器配置种子数据
-- 日期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_checkETL 数据更新后触发,检测助教服务记录匹配活跃任务
('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'
-- );

View File

@@ -1,50 +0,0 @@
-- =============================================================================
-- 迁移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不报溢出错误

View File

@@ -1,11 +0,0 @@
-- 2026-03-07 | 多实例任务隔离task_queue 增加 enqueued_by 列
-- 背景:发现有另一台机器(宿主机 D 盘部署)的后端也在消费同一个 task_queue
-- 导致任务被错误实例执行(路径不匹配 → UTF-8 解码失败)。
-- 通过 enqueued_by 列记录入队实例的 hostnameprocess_loop 只消费自己入队的任务。
ALTER TABLE task_queue ADD COLUMN IF NOT EXISTS enqueued_by VARCHAR(255) DEFAULT NULL;
COMMENT ON COLUMN task_queue.enqueued_by IS '入队实例的 hostnameplatform.node()),用于多实例任务隔离';
-- 回滚:
-- ALTER TABLE task_queue DROP COLUMN IF EXISTS enqueued_by;

View File

@@ -1,82 +0,0 @@
-- =============================================================================
-- 迁移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='客户基础信息' 的行需更新为 '客户基础'
-- - 后端 APIPOST /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 约束违反错误

View File

@@ -1,65 +0,0 @@
-- ============================================================
-- 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 '目标 IDApp2=时间维度编码 / 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);

View File

@@ -1,13 +0,0 @@
-- 给 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;

View File

@@ -1,29 +0,0 @@
-- 触发器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;