"""一次性回填:RIIN Goods.status 为 NULL 的历史行(终态"已发货"订单)。 背景:RIIN 商品级 goods_status 复用平台状态枚举,COMPLETE/PAUSE 此前无映射, 约 1/3 商品行 status 被写为 NULL(mappers.py 已补映射)。本脚本按订单终态 将近似值回填;"已关闭"与非终态订单不在此列,走 riin-by-orders 重拉取真值。 幂等:只更新 status IS NULL 的行。分批提交,避免长事务。 运行:uv run python scripts/backfill_riin_goods_status.py [--dry-run] [--batch N] """ from __future__ import annotations import argparse import os import psycopg2 from dotenv import load_dotenv load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env")) DEFAULT_BATCH = 50_000 SQL_COUNT = """ SELECT count(*) FROM "Goods" g JOIN "Orders" o ON o."orderNo" = g."orderNo" AND o."createdAt" = g."createdAt" WHERE o."systemName" = 'RIIN' AND o."systemStatus" = '已发货' AND g."status" IS NULL """ # ctid 分批:PostgreSQL 的 UPDATE 不支持 LIMIT,用子查询圈定每批行 SQL_BACKFILL_BATCH = """ UPDATE "Goods" g SET "status" = '已发货' WHERE g.ctid IN ( SELECT g2.ctid FROM "Goods" g2 JOIN "Orders" o ON o."orderNo" = g2."orderNo" AND o."createdAt" = g2."createdAt" WHERE o."systemName" = 'RIIN' AND o."systemStatus" = '已发货' AND g2."status" IS NULL LIMIT %s ) """ def main() -> None: parser = argparse.ArgumentParser(description="回填 RIIN Goods.status NULL(已发货订单)") parser.add_argument("--dry-run", action="store_true", help="只统计待回填行数,不写入") parser.add_argument("--batch", type=int, default=DEFAULT_BATCH, help="每事务回填行数") args = parser.parse_args() conn = psycopg2.connect(os.environ["DB_DSN"]) try: with conn.cursor() as cur: cur.execute(SQL_COUNT) (pending,) = cur.fetchone() print(f"[backfill] 待回填(status IS NULL 且订单已发货): {pending} 行") if args.dry_run or pending == 0: return total = 0 while True: with conn.cursor() as cur: cur.execute(SQL_BACKFILL_BATCH, (args.batch,)) updated = cur.rowcount conn.commit() total += updated print(f"[backfill] 本批 {updated} 行,累计 {total}") if updated < args.batch: break print(f"[backfill] 完成,共回填 {total} 行") finally: conn.close() if __name__ == "__main__": main()