如何修复django.db.utils.IntegrityError: 重复键值违反唯一约束?

4 投票
2 回答
7117 浏览
提问于 2025-04-18 05:12

我遇到了以下错误:

django.db.utils.IntegrityError: duplicate key value violates unique constraint "record_coordinates_lat_lon_created_by_id_key"
DETAIL:  Key (lat, lon, created_by_id)=(34.84000015258789, -111.80000305175781, 2) already exists.

背景:到目前为止,我一直在用Django 1.4.3和MySQL。现在我安装了Postgres 9.3和Psycopg2 2.5.2。验证和Syncdb都运行得很好。South没有安装。

我运行了一个脚本(这个脚本在MySQL上能正常工作)。这个脚本会循环处理GPS文件,并把经纬度数据保存到一个坐标表里。

_coordinates, coordinates_created = Coordinates.objects.get_or_create(
    lat=round(group[u'lat'], Coordinates.max_decimal_places),
    lon=round(group[u'lon'], Coordinates.max_decimal_places),
    created_by=self._user,
    modified_by=self._user,
    date_created=datetime.now(),  # See Update 2 addition below.
)

在模型定义中,我有一个unique_together = ('lat', 'lon', )的约束。因为有些坐标是相同的(所以我用了get_or_create())。我很困惑,因为它应该是“获取”这些坐标,而不是试图“创建”新的坐标。

这个网站上关于Postgres和Django的问题几乎都提到South。我需要South吗,还是这里发生了其他事情?我只是想快速测试一下,不想安装迁移工具。

更新1:我尝试运行了:SELECT setval('django_content_type_id_seq', (SELECT MAX(id) FROM django_content_type));,这是在Postgres中根据另一篇帖子的建议。错误依然存在。

更新2:我没意识到我需要把所有的坐标字段放到defaults字典里。坐标模型里还有一个字段'date_created=models.DateTimeField(auto_now_add=True)'。

我找到了一篇博客文章,似乎解释了当你使用'auto_now_add=True'时,get_or_create()会出问题。现在最大的问题是,我该如何在不破坏get_or_create()的情况下使用auto_now_add?

2 个回答

2

你在调用 get_or_create 的时候,漏掉了 defaults 这个参数。如果数据库里没有你指定的 latlon 的记录,它就会用默认的 latlon 创建一条新记录,而这些默认值显然不是自动生成的,这样就会导致 IntegrityError 的错误。

_coordinates, coordinates_created = Coordinates.objects.get_or_create(
    lat=round(group[u'lat'], Coordinates.max_decimal_places),
    lon=round(group[u'lon'], Coordinates.max_decimal_places),
    created_by=self._user,
    defaults=dict(
        lat=round(group[u'lat'], Coordinates.max_decimal_places),
        lon=round(group[u'lon'], Coordinates.max_decimal_places),
        created_by=self._user,
        modified_by=self._user,
    )
)

根据错误信息中的索引名,唯一索引是由 latloncreated_by 这几列组成的,所以你在 get_or_create 的过滤条件中应该把这三者都用上。

3

这解决了我的问题。

_coordinates, coordinates_created = Coordinates.objects.get_or_create(            
    lat=Decimal(group[u'lat'])._rescale(-Coordinates.max_decimal_places, 'ROUND_HALF_EVEN'),
    lon=Decimal(group[u'lon'])._rescale(-Coordinates.max_decimal_places, 'ROUND_HALF_EVEN'),
    created_by=self._user,
    modified_by=self._user,         
)

我用的 auto_nowauto_now_add 都没问题。结果发现,我的默认值已经在模型里定义好了。

问题出在 group[u'lat']group[u'lon'] 这两个值在我放进字典的时候被当成了浮点数(float)。而实际上,latlon 在模型里是定义为 DecimalFields() 的。

在使用 MySQL 的时候,我可以把这些浮点数和数据库里的内容比较得很好。但是,当我用 Postgres 时,get_or_create() 里的 get() 部分试图把数据库里的 Decimal 值和我提供的浮点数进行比较。这里的类型比较得更严格,所以浮点数在比较时不会被转换成 Decimal。

在我的调试工具里,我看到:

{Decimal}lat
{float}group[lat]

如果 Django 能给出一个清晰的错误提示,比如 TypeError: Can't compare Decimal with float. 就好了。

撰写回答