完整性错误:违反唯一约束条件的重复键值 - Django/PostgreSQL

92

关于我之前提出的问题,我在寻求将一个笨拙/写得很差的mysql查询转换为postgresql。我相信我已经成功了。无论如何,我正在使用手动从mysql数据库移动到postgresql数据库的数据。我正在使用以下查询:

  UPDATE krypdos_coderound cru

  set is_correct = case 
      when t.kv_values1 = t.kv_values2 then True 
      else False 
      end

  from 
  
  (select cr.id, 
    array_agg(
    case when kv1.code_round_id = cr.id 
    then kv1.option_id 
    else null end 
    ) as kv_values1,

    array_agg(
    case when kv2.code_round_id = cr_m.id 
    then kv2.option_id 
    else null end 
    ) as kv_values2

    from krypdos_coderound cr
     join krypdos_value kv1 on kv1.code_round_id = cr.id
     join krypdos_coderound cr_m 
       on cr_m.object_id=cr.object_id 
       and cr_m.content_type_id =cr.content_type_id 
     join krypdos_value kv2 on kv2.code_round_id = cr_m.id

   WHERE
     cr.is_master= False
     AND cr_m.is_master= True 
     AND cr.object_id=%s 
     AND cr.content_type_id=%s 

   GROUP BY cr.id  
  ) t

where t.id = cru.id
    """ % ( self.object_id, self.content_type.id)
  )

我有理由相信这很有效。然而,这导致了一个新问题。在尝试提交时,我从django获得一个错误,提示:

IntegrityError at (some url): 
duplicate key value violates unique constraint "krypdos_value_pkey"

我看了这里发布的几个回答,但还没有找到解决我的问题的方法(虽然相关问题很有趣)。我在日志中看到了这个,这很有趣,因为我从来没有显式地调用insert- django必须处理它:

   STATEMENT:  INSERT INTO "krypdos_value" ("code_round_id", "variable_id", "option_id", "confidence", "freetext")
   VALUES (1105935, 11, 55, NULL, E'') 
   RETURNING "krypdos_value"."id"

然而,尝试运行该代码会导致重复键错误。实际的错误信息在下面的代码中抛出。

# Delete current coding
CodeRound.objects.filter(
    object_id=o.id, content_type=object_type, is_master=True
).delete()
code_round = CodeRound(
    object_id=o.id, 
    content_type=object_type, 
    coded_by=request.user, comments=request.POST.get('_comments',None), 
    is_master=True,
)
code_round.save()
for key in request.POST.keys():
    if key[0] != '_' or key != 'csrfmiddlewaretoken':
        options = request.POST.getlist(key)
        for option in options:
            Value(
                code_round=code_round, 
                variable_id=key, 
                option_id=option,
                confidence=request.POST.get('_confidence_'+key, None),
            ).save()  #This is where it dies
# Resave to set is_correct
code_round.save()
o.status = '3' 
o.save()

我已检查了序列以及其他内容,它们似乎是有序的。到这一步,我不确定该怎么办——我认为这是Django端的问题,但我不确定。非常感谢任何反馈!


2
顺便提一下:根据德摩根定律,您的条件key[0] != '_' or key != 'csrfmiddlewaretoken'等同于not (key[0] == '_' and key == 'csrfmiddlewaretoken')。很容易看出内部条件永远不会被满足,因此它等同于not (False),换句话说就是True。那么为什么还要使用if呢? - Jonas Kölker
python manage.py sqlsequencereset <app> | python manage.py dbshell - user
此前的回答提供了更详细的信息和解释:https://dev59.com/eXVC5IYBdhLWcg3wlyQo - RedSands
14个回答

191

我也碰到过这个问题 - 原来你需要在Postgres中重新同步你的主键字段。关键是这个SQL语句:

SELECT setval('tablename_id_seq', (SELECT MAX(id) FROM tablename)+1);

就是这样!我以为我解决了那个问题,但事实证明我重置了错误的值。 - the_man_slim
1
我知道很多人都遇到过这个问题 - 很高兴你解决了它! - Hacking Life
我正要问@HackingLife,你是否知道更多关于为什么会发生这种情况的信息...结果对我们来说,原因是我们通过直接复制将所有数据从另一个数据库同步过来(正如zaphod在他的回答中提到的)。当我们停止这样做,开始直接使用主数据库时,当添加新模型时,主键序列没有被递增,导致了这个错误。 - AJP
我在手动通过SQL脚本导入内容后遇到了同样的问题,当我返回我的Flask UI添加新项时出现问题。更新主键字段序列为我解决了这个问题。感谢您的修复! - Joshua Powell
当您的表格没有'tablename_id_seq',只有'tablename'时,这个是如何工作的? - dwen
显示剩余2条评论

45

在MySQL和SQLite等数据库后端中存在一种已知的行为差异(即它们甚至在插入具有显式id的对象时也会更新下一个可用的主键),但像Postgres、Oracle等其他后端则不会。

有一个描述相同问题的票据。尽管它被关闭为无效,但它提供了一个提示,即Django管理命令可以更新下一个可用密钥。

要显示更新应用程序MyApp的所有下一个ID的SQL:

python manage.py sqlsequencereset MyApp
为了执行该语句,您可以将其作为输入提供给 dbshell 管理命令。对于 bash,您可以输入以下内容:
python manage.py sqlsequencereset MyApp | python manage.py dbshell

管理命令的优点在于抽象了底层的数据库后端,因此即使以后迁移到不同的后端,它也可以正常工作。


哇,就像魔法一样。 - kazem qanati

35

我在我的“库存”应用程序中有一个现有的表,我想在Django管理界面中添加新记录,但是出现了以下错误:

违反唯一约束条件“inventory_part_pkey”的重复键值 详细信息:关键字(part_id)=(1)已经存在。

如前所述,我运行以下代码以获取SQL命令来重置id:

python manage.py sqlsequencereset inventory

python manage.py sqlsequencereset inventory | python manage.py dbshell 这个命令输送到 shell 中并不起作用。

  • 所以我复制了生成的原始 SQL 命令
  • 然后打开了用于 PostgreSQL 的 pgAdmin3 https://www.pgadmin.org 并打开了我的数据库
  • 点击第6个图标(执行任意 SQL 查询)
  • 复制生成的语句

在我的情况下,原始的 SQL 命令是:

BEGIN;
SELECT setval(pg_get_serial_sequence('"inventory_signup"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "inventory_signup";
SELECT setval(pg_get_serial_sequence('"inventory_supplier"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "inventory_supplier";
COMMIT;

按F5键执行它。

这解决了所有问题。


2
这个答案应该是你实际需要的 - 这是最正确的方式! - Dmitry Arkhipenko
救了我的一天!谢谢。现在可以在“工具”菜单>“查询工具”下执行任意SQL查询。 - Chandragupta Borkotoky

9
除了zapphods的回答之外:
在我的情况下,索引确实不正确,因为我在开发时删除了所有迁移,并且可能删除了数据库10-15次,因为我还没有迁移任何内容。
我在finished_product_template_finishedproduct_pkey上遇到了IntegrityError错误。
重新索引表并重新启动runserver:
我使用的是pgadmin3,对于任何不正确并引发重复关键字错误的索引,我导航到constraints并重新索引。
然后重新索引。 enter image description here enter image description here

重新索引对我没有起作用,但是全面分析(同时选中冻结和分析选项)对我有帮助。我的表上没有任何序列或触发器,但我发现某些未完成的插入操作卡在了某个地方,所以全面清理帮了我大忙。 - MrLehiste

8
解决方法是需要重新同步您的主键字段,正如“Hacking Life”所报告的那样,他编写了一个示例SQL代码,但是,正如“Ad N”建议的那样,最好运行Django命令sqlsequencereset以获得确切的SQL代码,您可以复制并粘贴或使用另一个命令运行。
作为对这些答案的进一步改进,我建议您和其他读者不要复制和粘贴SQL代码,而是更安全地在您的Python代码中执行由sqlsequencereset生成的SQL查询,方式如下(使用默认数据库):
from django.core.management.color import no_style
from django.db import connection

from myapps.models import MyModel1, MyModel2


sequence_sql = connection.ops.sequence_reset_sql(no_style(), [MyModel1, MyModel2])
with connection.cursor() as cursor:
    for sql in sequence_sql:
        cursor.execute(sql)

我使用Python3.6Django 2.0PostgreSQL 10测试了这段代码。


请不要在多个问题中发布完全相同的答案。请发布一个好的答案,然后投票/标记其他问题为重复。如果该问题不是重复的,请根据问题进行调整您的答案。 - Martijn Pieters

5
如果你像我一样想要重置所有表的主键,可以使用PostgreSQL推荐的方式
SELECT 'SELECT SETVAL(' ||
       quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
       ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
       quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
     pg_depend AS D,
     pg_class AS T,
     pg_attribute AS C,
     pg_tables AS PGT
WHERE S.relkind = 'S'
    AND S.oid = D.objid
    AND D.refobjid = T.oid
    AND D.refobjid = C.attrelid
    AND D.refobjsubid = C.attnum
    AND T.relname = PGT.tablename
ORDER BY S.relname;

运行此查询后,您需要执行查询结果。我通常会将其复制并粘贴到记事本中。然后,我会查找和替换“SELECTSELECT;;。我将其复制并粘贴到pgAdmin III中并运行查询。它会重置数据库中的所有表。更多“专业”的说明请参见上面的链接。

3

3
我遇到了这个错误,因为我以错误的方式向保存方法传递了额外的参数。
对于任何遇到此问题的人,请尝试使用以下命令强制更新:
```python save(update_fields=[...]) ```
请注意保持HTML标签不变。
instance_name.save(..., force_update=True)

如果你收到一个错误消息,指出你不能同时传递 force_insertforce_update,那么你可能正在错误地传递一些自定义的参数,就像我曾经这样做的一样。

这是 Django 中的一个不错解决方案。只需从 shell 导入您的模型类(如果您没有使用 shell_plus),然后执行以下操作:MyModelClass.objects.first().save(force_update=True) - Doug Bradshaw
在我的情况下,我不得不同时使用force_insert和force_update,因为我覆盖了保存方法并调用了两次super。在第一次保存时,将force_insert设置为True,将force_update设置为False。在第二次保存时,将force_insert设置为False,将force_update设置为True。 - Cornel Ciobanu
那么,错误的实际原因是什么?它说“我以错误的方式向保存方法传递了额外的参数”,但没有给出任何细节。也许使用force_update的“蛮力”解决方法并不总是合适的。 - djvg

1

这个问题是在大约9年前提出的,很多人都提供了自己的解决方法。

对于我来说,在我的email自定义模型字段中添加了unique=True,但在创建超级用户时没有要求email是必填项。

现在,在创建超级用户后,我的电子邮件字段只保存为空白或Null。现在,这是我创建和保存新用户的方式

obj = mymodel.objects.create_user(username='abc', password='abc')
obj.email = 'abc@abc.com'
obj.save()

这个错误是因为默认情况下电子邮件被设置为空,这与管理员用户相同。所以在第一行就出现了 duplicate-key-value-violates 错误。 Django 检测到重复项!

解决方法

  • 选项1:创建任何用户时(包括超级用户),都要求输入电子邮件地址。
  • 选项2:删除 unique=True 并运行迁移。
  • 选项3:如果您不知道重复项在哪里,您可以删除该列或使用 python manage.py flush 清除数据库。

强烈建议您知道错误发生的原因。


0
我曾经遇到过类似的问题,但是似乎什么都不起作用。如果你需要数据(即在进行转储时不能排除它),请确保已关闭(注释掉)任何post_save接收器。我认为数据会被导入,但由于这些原因会再次创建相同的模型。对我有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接