Django迁移PostgreSQL数据库时触发UniqueViolation错误:cpf_funcionario字段存在重复空值
解决Django迁移时PostgreSQL唯一索引创建失败的问题
问题根源
你遇到的django.db.utils.IntegrityError本质是PostgreSQL的唯一约束规则导致的:当你给Funcionario模型的cpf_funcionario字段添加unique=True后,Django会尝试在数据库创建唯一索引,但你的models_funcionario表中已经存在多条cpf_funcionario为空值的记录。在PostgreSQL中,空值会被视为相等的,所以这些重复的空值违反了唯一约束,导致索引创建失败。
解决方案步骤
1. 确认数据库中的重复数据
首先连接到你的PostgreSQL数据库,执行以下SQL查询,验证空值记录的数量:
SELECT cpf_funcionario, COUNT(*) FROM models_funcionario GROUP BY cpf_funcionario HAVING COUNT(*) > 1;
你会看到cpf_funcionario为空的记录条数大于1,这就是问题的核心。
2. 处理重复的空值数据
根据你的业务需求,选择以下两种方案之一:
方案A:给空值记录填充唯一的CPF值
如果业务要求每个员工必须有唯一的CPF,你需要手动给这些空值记录补充合法且唯一的CPF:
-- 示例:给指定ID的空值记录设置CPF UPDATE models_funcionario SET cpf_funcionario = '123.456.789-00' WHERE cpf_funcionario IS NULL AND id = 1; -- 重复此操作,给所有空值记录分配唯一的CPF
完成数据修复后,重新执行迁移命令:
(env)$ python manage.py migrate
方案B:允许CPF为空但仅非空值唯一(业务允许的情况)
如果业务允许员工暂时没有CPF,但要求非空的CPF必须唯一,你需要修改模型的字段定义,使用部分唯一索引来绕过空值的冲突:
首先修改Funcionario模型:
from django.db.models import Q, Index class Funcionario(AbstractUser): rg_funcionario = models.CharField(max_length=200) # 保留null=True和blank=True,移除原有的unique=True cpf_funcionario = models.CharField(max_length=200, null=True, blank=True) id_funcao_funcionario = models.ForeignKey(FuncaoFuncionario, on_delete=models.SET_NULL, null=True) # ... 其他字段保持不变 class Meta: # 添加部分唯一索引:仅当cpf_funcionario非空时强制唯一 indexes = [ Index(fields=['cpf_funcionario'], condition=Q(cpf_funcionario__isnull=False), name='unique_cpf_not_null') ]
然后重新生成迁移文件并执行:
(env)$ python manage.py makemigrations (env)$ python manage.py migrate
3. 回滚错误的迁移(可选)
如果你已经生成了导致问题的0018_auto_20210809_1124迁移文件,可以先回滚到上一个成功的迁移状态,再处理数据或修改模型:
(env)$ python manage.py migrate models 0017_alter_horarioservico_unidade
额外提示
- 后续添加唯一约束前,一定要先检查现有数据是否符合约束要求,避免类似问题。
- PostgreSQL对空值在唯一约束中的处理逻辑和MySQL不同,这点需要特别注意哦。
内容的提问来源于stack exchange,提问作者LucasBorges-Santos
相关产品推荐
相关产品推荐

