-- 006_accommodation_history_constraint.sql -- 目标:移除 train_accommodation 全局唯一约束 uq_accommodation_enroll, -- 创建部分唯一索引 uq_accommodation_enroll_active (每个 enrollment 最多 1 条有效住宿), -- 允许学员拥有多条退宿历史记录 (status=2),彻底支持调房与历史留痕。 DO $$ DECLARE v_dup_count INTEGER; v_con_def TEXT; v_idx_def TEXT; BEGIN -- 1. Pre-Migration Data Safety Guard -- 检查是否已存在同一个 enrollment_id 拥有 2 条或以上处于有效状态 (0=预分配, 1=已入住) 的住宿记录 SELECT COUNT(*) INTO v_dup_count FROM ( SELECT enrollment_id FROM train_accommodation WHERE accommodation_status IN (0, 1) GROUP BY enrollment_id HAVING COUNT(*) > 1 ) t; IF v_dup_count > 0 THEN RAISE EXCEPTION 'Pre-migration check failed: found % enrollment(s) with multiple active accommodations (status IN (0,1)). Manual resolution required before applying migration 006.', v_dup_count; END IF; -- 2. Existing Constraint Guard (Idempotent Drop) -- 检查约束 uq_accommodation_enroll 是否存在且符合预期 SELECT pg_get_constraintdef(c.oid) INTO v_con_def FROM pg_constraint c JOIN pg_class t ON c.conrelid = t.oid WHERE t.relname = 'train_accommodation' AND c.conname = 'uq_accommodation_enroll'; IF v_con_def IS NOT NULL THEN -- 验证旧约束是否为 UNIQUE (enrollment_id) IF v_con_def ILIKE 'UNIQUE (enrollment_id)%' THEN RAISE NOTICE 'Dropping legacy global unique constraint: uq_accommodation_enroll'; ALTER TABLE train_accommodation DROP CONSTRAINT uq_accommodation_enroll; ELSE RAISE EXCEPTION 'Constraint collision: constraint uq_accommodation_enroll exists but definition is unexpected: %', v_con_def; END IF; ELSE RAISE NOTICE 'Constraint uq_accommodation_enroll does not exist, skipping drop.'; END IF; -- 3. New Index Collision Guard & Creation -- 检查部分唯一索引 uq_accommodation_enroll_active SELECT pg_get_indexdef(i.indexrelid) INTO v_idx_def FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid JOIN pg_class t ON i.indrelid = t.oid WHERE t.relname = 'train_accommodation' AND c.relname = 'uq_accommodation_enroll_active'; IF v_idx_def IS NOT NULL THEN -- 验证已有索引是否为预期的部分唯一索引 IF v_idx_def ILIKE '%UNIQUE INDEX uq_accommodation_enroll_active ON %train_accommodation%enrollment_id%WHERE%accommodation_status%' THEN RAISE NOTICE 'Partial unique index uq_accommodation_enroll_active already exists with expected definition, skipping creation.'; ELSE RAISE EXCEPTION 'Index collision: index uq_accommodation_enroll_active exists but definition differs: %', v_idx_def; END IF; ELSE RAISE NOTICE 'Creating partial unique index: uq_accommodation_enroll_active'; CREATE UNIQUE INDEX uq_accommodation_enroll_active ON train_accommodation (enrollment_id) WHERE accommodation_status IN (0, 1); END IF; END $$;