-- 2026-10-16-01-brands-renumber-imported-ids.sql -- -- 背景:2026-10-16 从 app_brands(MySQL dump)导入的 23218 个品牌带的是**源系统的显式 id** -- (3097..26472)。而 brands.id 本身是 IDENTITY 列(GENERATED ... AS IDENTITY,序列 -- brands_id_seq),导入后序列仍停在 30 —— 新增品牌会从 31 开始编号,一旦涨到 3097 -- 就会与这批导入行**撞主键**。本迁移把那批行重排进真正的自增空间,并把序列对齐过去。 -- -- 安全性(迁移前已核对):全库没有任何行引用这些 id —— -- * brand_runways.brand_id 最大只有 7(115 场走秀全部落在 19 个种子品牌内); -- * favorites / histories 存的是内容(走秀/街拍)快照,不存品牌 id。 -- 19 个种子品牌(id ≤ 19)完全不动。 -- -- 三步:① 整批挪到负区间(避开主键中途相撞)② 按原 id 升序落回 20 起的连续区间 -- ③ 把 IDENTITY 序列对齐到新的最大值(下一次 nextval 即 max+1)。 -- -- ⚠️ 第 ② 步的 JOIN 必须用「当前的负数 id 本身」去匹配,**不能**拿子查询里的 -id(正数) -- 去比 b.id(负数)—— 那样一行都匹配不上,会留下满表负数 id 的半途状态(本文件初版就踩了)。 -- -- 幂等:仅当「种子(≤19) 与导入批次(≥3097) 之间存在空洞(20..3096 全空)」时才重排; -- 重排后 20..3096 已被占满,手动重跑会直接跳过。dbtool migrate 另按 schema_migrations 记账。 DO $$ DECLARE v_before_max integer; v_moved integer; v_after_max integer; BEGIN IF EXISTS (SELECT 1 FROM brands WHERE id BETWEEN 20 AND 3096) THEN RAISE NOTICE '[brands-renumber] 20..3096 非空,说明已重排过,跳过'; RETURN; END IF; -- 待处理的行:原始状态的 id >= 3097,或上一次中断留下的负数行。 IF NOT EXISTS (SELECT 1 FROM brands WHERE id < 0 OR id >= 3097) THEN RAISE NOTICE '[brands-renumber] 没有待重排的行,跳过'; RETURN; END IF; SELECT max(id) INTO v_before_max FROM brands; -- 1) 原始状态:整批挪到负区间(若已是负数则天然不匹配,空跑)。 UPDATE brands SET id = -id WHERE id >= 3097; GET DIAGNOSTICS v_moved = ROW_COUNT; -- 2) 按原 id 升序落回「紧接 19 个种子之后」的连续区间(排序固定,结果可复现)。 UPDATE brands b SET id = m.new_id FROM ( SELECT id AS neg_id, (row_number() OVER (ORDER BY -id))::integer + 19 AS new_id FROM brands WHERE id < 0 ) m WHERE b.id = m.neg_id; -- 3) 把 IDENTITY 序列对齐到新的最大值。 SELECT max(id) INTO v_after_max FROM brands; PERFORM setval(pg_get_serial_sequence('brands', 'id'), v_after_max); RAISE NOTICE '[brands-renumber] 完成:负区间挪动 % 行,id 最大值 % -> %,序列下次给出 %', v_moved, v_before_max, v_after_max, v_after_max + 1; END $$;