django update_or_create 得到“重复键值违反唯一约束”

Posted

技术标签:

【中文标题】django update_or_create 得到“重复键值违反唯一约束”【英文标题】:django update_or_create gets "duplicate key value violates unique constraint " 【发布时间】:2018-11-27 16:19:14 【问题描述】:

也许我误解了 Django 的 update_or_create Model 方法的目的。

这是我的模型:

from django.db import models
import datetime
from vc.models import Cluster

class Vmt(models.Model):
    added = models.DateField(default=datetime.date.today, blank=True, null=True)
    creation_time = models.TextField(blank=True, null=True)
    current_pm_active = models.TextField(blank=True, null=True)     
    current_pm_total = models.TextField(blank=True, null=True)
    ... more simple fields ...
    cluster = models.ForeignKey(Cluster, null=True)


    class Meta:
        unique_together = (("cluster", "added"),)

这是我的测试:

from django.test import TestCase
from .models import *
from vc.models import Cluster
from django.db import transaction


# Create your tests here.
class VmtModelTests(TestCase):
    def test_insert_into_VmtModel(self):
        count = Vmt.objects.count()
        self.assertEqual(count, 0)

        # create a Cluster
        c = Cluster.objects.create(name='test-cluster')
        Vmt.objects.create(
            cluster=c,
            creation_time='test creaetion time',
            current_pm_active=5,
            current_pm_total=5,
            ... more simple fields ...
        )
        count = Vmt.objects.count()
        self.assertEqual(count, 1)
        self.assertEqual('5', c.vmt_set.all()[0].current_pm_active)

        # let's test that we cannot add that same record again
        try:
            with transaction.atomic():

                Vmt.objects.create(
                    cluster=c,
                    creation_time='test creaetion time',
                    current_pm_active=5,
                    current_pm_total=5,
                    ... more simple fields ...
                )
                self.fail(msg="Should violated integrity constraint!")
        except Exception as ex:
            template = "An exception of type 0 occurred. Arguments:\n1!r"
            message = template.format(type(ex).__name__, ex.args)
            self.assertEqual("An exception of type IntegrityError occurred.", message[:45])

        Vmt.objects.update_or_create(
            cluster=c,
            creation_time='test creaetion time',
            # notice we are updating current_pm_active to 6
            current_pm_active=6,
            current_pm_total=5,
            ... more simple fields ...
        )
        count = Vmt.objects.count()
        self.assertEqual(count, 1)

在最后一次 update_or_create 调用中,我收到此错误:

IntegrityError: duplicate key value violates unique constraint "vmt_vmt_cluster_id_added_c2052322_uniq"
DETAIL:  Key (cluster_id, added)=(1, 2018-06-18) already exists.

为什么模型没有更新?为什么 Django 尝试创建违反唯一约束的新记录?

【问题讨论】:

update_or_create 包含过滤条件,在 defaults=.. 中指定要更新的字段。 因此,对于我需要更新的每个字段,我需要在 defaults 中指定该字段。 【参考方案1】:

你应该分开你的领域:

    应搜索的字段 应更新的字段

例如: 如果我有模型:

class User(models.Model):
    username = models.CharField(max_length=200)
    nickname = models.CharField(max_length=200)

我想搜索 username = 'Nikolas' 并将这个实例昵称更新为 'Nik'(如果没有用户名为 'Nikolas' 的用户,我需要创建它)我应该编写以下代码:

User.objects.update_or_create(
    username='Nik', 
    defaults='nickname': 'Nikolas',
)

见https://docs.djangoproject.com/en/3.1/ref/models/querysets/

【讨论】:

你的例子中的defaults不应该是字典吗?【参考方案2】:

上面已经很好地回答了这个问题。

为了更清楚,update_or_create() 方法应该有 **kwargs 作为您要通过过滤检查该数据是否已存在于 DB 中的参数。

select some_column from table_name where column1='' and column2='';

按 **kwargs 过滤将为您提供对象。现在,如果您希望更新这些过滤对象的任何数据/列,您应该在 update_or_create() 方法的默认参数中传递它们。

假设您现在基于过滤器找到了一个对象,默认参数值预计会被挑选和更新。

如果根据过滤器没有找到匹配的对象,那么它会继续创建一个带有过滤器的条目并传递默认参数。

【讨论】:

【参考方案3】:

update_or_create(defaults=None, **kwargs) 基本上有两个部分:

    **kwargs 指定“过滤器”标准以确定此类对象是否已存在;和 defaults 是一个字典,其中包含映射到值的字段应该更新(以防我们发现这样的行)。

这里的问题是你的过滤器过于严格:你添加了几个过滤器,结果数据库找不到这样的行。那么会发生什么?然后数据库旨在使用这些过滤器值创建行(并且由于缺少defaults,因此没有添加额外的值)。但事实证明,我们创建了一行,并且 clusteradded 的组合已经存在。因此数据库拒绝添加这一行。

所以这一行:

Model.objects.update_or_create(field1=val1,
                               field2=val2,
                               defaults=
                                   'field3': val3,
                                   'field4': val4
                               )

在语义上近似等于:

try:
    item = Model.objects.get(field1=val1, field2=val2)
except Model.DoesNotExist:
    Model.objects.create(field1=val1, field2=val2, field3=val3, field4=val4)
else:
    item = Model.objects.filter(
        field1=val1,
        field2=val2,
    ).update(
        field3 = val3
        field4 = val4
    )

(但原始调用通常在单个查询中完成)。

你可能应该这样写:

Vmt.objects.update_or_create(
    cluster=c,
    creation_time='test creaetion time',
    defaults =         
        'current_pm_active': 6,
        'current_pm_total': 5,
    
)

(或类似的)

【讨论】:

感谢您的清晰解释。我试图在我的单元测试中重现生产中将发生的事情。 Vmt 模型的数据来自我通过 URL 访问的 CSV 文件。如果此 cvs 文件中有新行,我想创建新的 Vmt 记录,但如果行已更改,我想更新读取 cvs 文件当天的 Vmt 记录。 @RedCricket:好吧,creation_time 可能是这里的“罪魁祸首”。就我个人而言,我认为将这些独特的东西放在一起有点问题,因为通常时间是不断增加的东西。所以这意味着有时你会创建一个副本,有时你不会。这是相当“不稳定”。 create_time 只是一个字符串,不属于对数据库表的任何约束。 @RedCricket:嗯,好吧,那么这应该可行。因此,如果已经存在具有给定clustercreation_timeVmt,我们更新该行,否则我们创建一个。 模型中的 unique_together 指定 clusteradded。我希望这将确保我在给定的一天只有一组给定集群的 Vmt 数据。

以上是关于django update_or_create 得到“重复键值违反唯一约束”的主要内容,如果未能解决你的问题,请参考以下文章

django单表操作中update_or_create不能更新多于一个数据的信息

django update_or_create 得到“重复键值违反唯一约束”

Django 2021年最新版教程17数据库操作 models 存在更新 不存在新建update_or_create

导入功能中的 update_or_create

是否有用于使用 VTL 的 AWS Appsync 的 Model.objects.update_or_create()?

Django 批处理/批量更新或创建?