Files
Neo-ZQYY/scripts/ops/run_migration_staff_info.py

190 lines
5.5 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
在测试库 test_etl_feiqiu 执行员工档案建表迁移脚本。
迁移脚本db/etl_feiqiu/migrations/2026-02-22__add_staff_info_tables.sql
目标表ods.staff_info_master, dwd.dim_staff, dwd.dim_staff_ex
使用方式:
python scripts/ops/run_migration_staff_info.py
"""
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
import psycopg2
# 加载根 .env
_ROOT = Path(__file__).resolve().parents[2]
load_dotenv(_ROOT / ".env", override=False)
DSN = os.getenv("TEST_DB_DSN")
if not DSN:
print("ERROR: TEST_DB_DSN 未配置,请在根 .env 中设置")
sys.exit(1)
MIGRATION_FILE = (
_ROOT / "db" / "etl_feiqiu" / "migrations"
/ "2026-02-22__add_staff_info_tables.sql"
)
# 需要创建的三张表
TABLES = [
("ods", "staff_info_master"),
("dwd", "dim_staff"),
("dwd", "dim_staff_ex"),
]
def tables_exist(conn) -> dict[str, bool]:
"""检查目标表是否已存在,返回 {schema.table: bool}"""
cur = conn.cursor()
result = {}
for schema, table in TABLES:
cur.execute("""
SELECT 1 FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""", (schema, table))
result[f"{schema}.{table}"] = cur.fetchone() is not None
cur.close()
return result
def execute_migration(conn) -> bool:
"""执行迁移脚本,返回是否成功"""
sql = MIGRATION_FILE.read_text(encoding="utf-8")
# 去掉注释中的回滚部分
main_lines = []
in_rollback = False
for line in sql.split("\n"):
stripped = line.strip()
if stripped.startswith("-- ====") and "回滚" in stripped:
in_rollback = True
if not in_rollback:
main_lines.append(line)
main_sql = "\n".join(main_lines).strip()
if not main_sql:
print("⚠️ 迁移脚本为空,跳过")
return False
try:
cur = conn.cursor()
cur.execute(main_sql)
cur.close()
print("✅ 迁移脚本执行成功")
return True
except Exception as e:
print(f"❌ 迁移脚本执行失败: {e}")
return False
def verify(conn) -> bool:
"""验证建表结果"""
cur = conn.cursor()
checks = []
# 1. 三张表都存在
for schema, table in TABLES:
cur.execute("""
SELECT 1 FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""", (schema, table))
checks.append((f"{schema}.{table} 存在", cur.fetchone() is not None))
# 2. ods.staff_info_master 关键字段
cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'ods' AND table_name = 'staff_info_master'
""")
ods_cols = {r[0] for r in cur.fetchall()}
ods_required = {"id", "staff_name", "mobile", "content_hash", "payload", "tenant_id", "site_id"}
missing_ods = ods_required - ods_cols
checks.append((f"ODS 关键字段完整({len(ods_cols)} 列)", len(missing_ods) == 0))
if missing_ods:
print(f" ODS 缺失字段: {missing_ods}")
# 3. dwd.dim_staff 主键包含 staff_id + scd2_start_time
cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'dwd' AND table_name = 'dim_staff'
""")
dwd_cols = {r[0] for r in cur.fetchall()}
dwd_required = {"staff_id", "staff_name", "scd2_start_time", "scd2_end_time", "scd2_is_current"}
missing_dwd = dwd_required - dwd_cols
checks.append((f"DWD 主表关键字段完整({len(dwd_cols)} 列)", len(missing_dwd) == 0))
if missing_dwd:
print(f" DWD 主表缺失字段: {missing_dwd}")
# 4. dwd.dim_staff_ex 关键字段
cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'dwd' AND table_name = 'dim_staff_ex'
""")
ex_cols = {r[0] for r in cur.fetchall()}
ex_required = {"staff_id", "rank_name", "shop_name", "scd2_start_time"}
missing_ex = ex_required - ex_cols
checks.append((f"DWD 扩展表关键字段完整({len(ex_cols)} 列)", len(missing_ex) == 0))
if missing_ex:
print(f" DWD 扩展表缺失字段: {missing_ex}")
cur.close()
print("\n" + "=" * 50)
print("建表验证结果")
print("=" * 50)
all_ok = True
for name, ok in checks:
status = "" if ok else ""
print(f" {status} {name}")
if not ok:
all_ok = False
return all_ok
def main():
dsn_display = DSN.split("@")[1] if "@" in DSN else DSN
print(f"连接测试库: {dsn_display}")
print(f"迁移脚本: {MIGRATION_FILE.name}\n")
if not MIGRATION_FILE.exists():
print(f"ERROR: 迁移脚本不存在: {MIGRATION_FILE}")
sys.exit(1)
conn = psycopg2.connect(DSN)
conn.autocommit = True
# 检查表是否已存在
existing = tables_exist(conn)
all_exist = all(existing.values())
if all_exist:
print(" 所有目标表已存在,跳过建表")
else:
for name, exists in existing.items():
if exists:
print(f" {name} 已存在")
else:
print(f" 📋 {name} 待创建")
if not execute_migration(conn):
conn.close()
sys.exit(1)
# 验证
all_ok = verify(conn)
conn.close()
if all_ok:
print("\n✅ 员工档案建表迁移完成,所有验证通过")
else:
print("\n⚠️ 部分验证未通过,请检查")
sys.exit(1)
if __name__ == "__main__":
main()