独特,涉及多个外键和多个字段

sha*_*k3r 14 python django postgresql database-design django-models

我们公司的定价取决于多个参数,现在我们想为Django中的现有设置引入另一个可能的M2M参数。

为此,我们有一个现有的定价表,该表对unique_together除之外的所有字段都有约束price_field。在示例中为通用/基于字母的命名表示歉意。

class PricingTable(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)
    price = MoneyField()
    b = ArrayField(models.CharField(choices=CHOICES))
    c = models.ForeignKey(C, on_delete=models.CASCADE)

    class Meta:
        ordering = ("a",)
        unique_together = ("a", "b", "c")

    def validate_b(self):
        # b can't be empty
        if not len(self.b) >= 1:
            raise ValueError
        # each element in b needs to be unique
        if not len(self.b) == len(set(self.b)):
            raise ValueError
        # each element in b needs to be unique together with a & c
        for i in self.b:
            query = PricingTable.objects.filter(
                a=self.a, c=self.c, b__contains=[i]
            ).exclude(pk=self.pk)
            if query.count() > 0:
                raise ValueError

    def save(self, *args, **kwargs):
        self.validate_b()
        return super().save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

我想向该表中引入另一个参数,该参数必须与非价格参数(abc)一起唯一。

d = models.ManyToManyField("x.D", related_name="+")
Run Code Online (Sandbox Code Playgroud)

列表中的每个元素b都必须与a&一起唯一c

上面的问题是,validate_b必须通过繁重的数据库查询将功能升级为可能复杂的功能。Django并没有提供直接的方法来确保多对多字段的唯一性。

因此,我应该尝试使用其他方法吗?一个through吧?但是,我应该在穿透表中包括哪些所有字段?所有非价格字段?还是我应该停止梦想拥有众多领域,d并继续采用简单的外键方法,并unique_together在所有这些方面都直截了当?

版本:

  • Django 2.2
  • Postgres 11

如果需要,我可以将现有的ArrayField转换为简单的数组CharField,这意味着会有更多的数据库行,只要我将所有唯一的约束都放入数据库中,而不是每次都进行保存验证,就可以了。

sha*_*k3r 1

由于只有少数d实体必须有相应的价格(其余实体将继续具有通用的共同价格),因此我最终采用了以下结构。

class PricingTable(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)
    price = MoneyField()
    b = ArrayField(models.CharField(choices=CHOICES))
    c = models.ForeignKey(C, on_delete=models.CASCADE)
    d = models.ForeignKey("x.D", on_delete=models.CASCADE, blank=True, null=True)

    class Meta:
        ordering = ("a",)
        unique_together = ("a", "b", "c", "d")

    def validate_b(self):
        # b can't be empty
        if not len(self.b) >= 1:
            raise ValueError
        # each element in b needs to be unique
        if not len(self.b) == len(set(self.b)):
            raise ValueError
        # each element in b needs to be unique together with a, c & d
        query = PricingTable.objects.filter(
            a=self.a, c=self.c, d=self.d, b__overlap=self.b
        ).exclude(pk=self.pk)
        if query.count() > 0:
            raise ValueError

    def save(self, *args, **kwargs):
        self.validate_b()
        return super().save(*args, **kwargs)


class DBasedPricing(models.Model):
    """
    Lookup table that tells (row exists) if we have D based pricing coupled with param A
    If we do, query PricingTable.d=d, else PricingTable.d=None for correct pricing
    """
    d = models.ForeignKey("x.D", on_delete=models.CASCADE)
    a = models.ForeignKey(A, on_delete=models.CASCADE)

    class Meta:
        unique_together = ("d", "a")
Run Code Online (Sandbox Code Playgroud)

这迫使我首先根据参数进行查找d,以检查定价是否D基于

d_id = None
if DBasedPricing.objects.filter(d_id=input_param.d, a_id=a.id).exists():
    d_id = input_param.d
Run Code Online (Sandbox Code Playgroud)

然后向我的常用查询添加另一个参数

price_obj = PricingTable.objects.filter(...usual query..., d_id=d_id)
Run Code Online (Sandbox Code Playgroud)

总的来说,以一次简单的索引查找为代价,我节省了行数,当然还有复杂的数据库结构。另外,我最终不必重新输入所有现有的定价!