Files
dpb/backend/scripts/add_unique_constraints.py
T

89 lines
3.5 KiB
Python
Raw Normal View History

"""为育种核心表补「部分唯一索引(未软删)」并发兜底,幂等,可 --check 预览。
背景:combination_code / tree_no / cultivar_name / personnel.name / (site_id, plot_code)
无 DB 层唯一约束,service 层 select-then-insert 存在并发竞态。此处补部分唯一索引
WHERE is_deleted = false),与各 service 的查重语义(只查未软删)一致:
- 未软删记录之间不允许重复 → 并发插入同一编号仅一条成功,另一条 409。
- 已软删记录不参与约束 → 删除后仍可重新使用该编号。
用法(backend/ 目录):
python scripts/add_unique_constraints.py --check
python scripts/add_unique_constraints.py
"""
import argparse
import os
import sys
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
from sqlalchemy import create_engine, text
from app.config.setting import settings
# (表, 索引名, 唯一列, 说明)
UNIQUE_INDEXES = [
("bre_cross_combination", "uniq_bre_cross_combination_code", "combination_code", "组合编号"),
("bre_tree", "uniq_bre_tree_no", "tree_no", "单株编号"),
("bre_germplasm", "uniq_bre_germplasm_cultivar_name", "cultivar_name", "品种/种质名称"),
("bre_personnel", "uniq_bre_personnel_name", "name", "人员姓名"),
("bre_plot", "uniq_bre_plot_site_code", "site_id, plot_code", "基地地块编号"),
]
def main() -> None:
parser = argparse.ArgumentParser(description="为育种表补部分唯一索引(未软删)")
parser.add_argument("--check", action="store_true", help="仅打印将执行的 DDL,不落库")
args = parser.parse_args()
engine = create_engine(settings.DB_URI)
print(f"# 连接: {settings.DB_URI}")
with engine.connect() as conn:
for table, idx_name, cols, label in UNIQUE_INDEXES:
# 1) 未软删重复检查(部分索引只约束未软删行,故只查未软删)
dup_sql = (
f"SELECT {cols} FROM {table} "
"WHERE is_deleted = false "
f"GROUP BY {cols} HAVING COUNT(*) > 1"
)
try:
dups = conn.execute(text(dup_sql)).fetchall()
except Exception as e:
print(f"# [跳过] {table}: 查重失败 {e!s}")
continue
if dups:
print(f"# [跳过] {label}{table}.{cols})存在 {len(dups)} 组未软删重复,请先清洗数据:")
for d in dups[:10]:
print(" " + " / ".join(str(x) for x in d))
continue
# 2) 索引是否已存在
exists = conn.execute(
text("SELECT 1 FROM pg_indexes WHERE indexname = :name"),
{"name": idx_name},
).fetchone()
if exists:
print(f"# [已存在] {idx_name}")
continue
ddl = (
f"CREATE UNIQUE INDEX {idx_name} ON {table} ({cols}) "
"WHERE is_deleted = false"
)
print(" " + ddl)
if not args.check:
try:
with engine.begin() as c:
c.execute(text(ddl))
print("OK " + ddl)
except Exception as e:
print("ERR " + ddl + " -> " + str(e)[:160])
print("# --check 模式:未执行任何变更。" if args.check else "# 完成。")
if __name__ == "__main__":
main()