89 lines
3.5 KiB
Python
89 lines
3.5 KiB
Python
"""为育种核心表补「部分唯一索引(未软删)」并发兜底,幂等,可 --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()
|