如何修复django.db.utils.IntegrityError: 重复键值违反唯一约束?
我遇到了以下错误:
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 个回答
你在调用 get_or_create
的时候,漏掉了 defaults
这个参数。如果数据库里没有你指定的 lat
和 lon
的记录,它就会用默认的 lat
和 lon
创建一条新记录,而这些默认值显然不是自动生成的,这样就会导致 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,
)
)
根据错误信息中的索引名,唯一索引是由 lat
、lon
和 created_by
这几列组成的,所以你在 get_or_create
的过滤条件中应该把这三者都用上。
这解决了我的问题。
_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_now
和 auto_now_add
都没问题。结果发现,我的默认值已经在模型里定义好了。
问题出在 group[u'lat']
和 group[u'lon']
这两个值在我放进字典的时候被当成了浮点数(float)。而实际上,lat
和 lon
在模型里是定义为 DecimalFields()
的。
在使用 MySQL 的时候,我可以把这些浮点数和数据库里的内容比较得很好。但是,当我用 Postgres 时,get_or_create()
里的 get()
部分试图把数据库里的 Decimal 值和我提供的浮点数进行比较。这里的类型比较得更严格,所以浮点数在比较时不会被转换成 Decimal。
在我的调试工具里,我看到:
{Decimal}lat
{float}group[lat]
如果 Django 能给出一个清晰的错误提示,比如 TypeError: Can't compare Decimal with float.
就好了。