Django - 显示结果信息,同时使用具有多个外键关系的模型优化数据库查询

Posted

技术标签:

【中文标题】Django - 显示结果信息,同时使用具有多个外键关系的模型优化数据库查询【英文标题】:Django - Displaying result information while optimizing database queries with models that multiple foreign key relationships 【发布时间】:2013-06-25 05:48:09 【问题描述】:

所以我正在尝试整理一个网页,但我目前无法在我整理的 Web 应用程序中为每个用户整理一个结果页面。

这是我的模型的样子:

class Fault(models.Model):
    name = models.CharField(max_length=255)
    severity = models.PositiveSmallIntegerField(default=0)
    description = models.CharField(max_length=1024, null=False, blank=False)
    recommendation = models.CharField(max_length=1024, null=False, blank=False)
    date_added = models.DateTimeField(_('date added'), default=timezone.now)
    ...


class FaultInstance(models.Model):
    auto = models.ForeignKey(Auto)
    fault = models.ForeignKey(Fault)
    date_added = models.DateTimeField(_('date added'), default=timezone.now)

    objects = FaultInstanceManager()
    ...

class Auto(models.Model):
    label = models.CharField(max_length=255)
    model = models.CharField(max_length=255)
    make = models.CharField(max_length=255)
    year = models.IntegerField(max_length=4)
    user = models.ForeignKey(AUTH_USER_MODEL)
    ...

我不知道我的模特关系是否理想,但我认为这很有意义。因此,每个用户都可以拥有多个与之关联的 Auto 对象。而且每个Auto都可以有多个FaultInstance对象与之关联。

在结果页面中,我想列出用户在其Autos 中拥有的所有FaultInstances。在每个列出的 FaultInstance 下,我将列出用户拥有的所有有故障的汽车及其信息(这是我的想法)。

所有 FaultInstance 列表按严重性排序(从大到小)

FaultInstance:
   FaultDescription:
   FaultRecommendation:
   ListofAutosWithFault:
       AutoLabel AutoModel AutoYear ...
       AutoLabel AutoModel AutoYear ...

显然,以正确的方式做事意味着我想在 Python/Django 方面做尽可能多的列表创建,并避免在模板中做任何逻辑或处理。我可以使用模型管理器为每个严重性创建一个列表,如下所示:

class FaultInstanceManager(models.Manager):
    def get_faults_by_user_severity(self, user, severity):
        faults = defaultdict(list)
        qs_faultinst = self.model.objects.select_related().filter(
                auto__user=user, fault__severity=severity
            ).order_by('auto__make')
        for result in qs_faultinst:
            faults[result.fault].append(result)

        faults.default_factory = None
        return faults

我仍然需要指定每个严重性,但我想如果我只有 5 个严重性级别,我可以为每个严重性级别创建一个列表并将每个单独的级别传递给模板。对此的任何建议表示赞赏。然而,那不是我的问题。我现在的停止点是,我想在他们的报告顶部创建一个汇总表,它可以为用户提供每个品牌|型号|年份的故障实例细分。我想不出传递给模板的正确查询或数据结构。

摘要(所有 FaultInstances 的表格,带有以下列标题):

FaultInstance    Make|Model|Year    NumberOfAutosAffected

这会让我知道品牌、型号或年份的指标(在下面的示例中,它根据型号区分故障)。我列出了 FaultInstances,因为我只列出了连接到用户的 Faults

举例

Bad Starter     Nissan     1
Bad Tailight    Honda      2
Bad Tailight    Nissan     1

我是一个完美主义者,我想在优化数据库查询的同时做到这一点。如果我可以在我的原始查询中创建一个可以在模板中轻松解析的数据结构,并且仍然在我的报告中获得这两个部分(可能是 defaultdict(list) 的 defaultdict),这就是我想要做的。感谢您的帮助,希望我的问题是彻底且有意义的。

【问题讨论】:

【参考方案1】:

使用相关名称是有意义的,因为它可以简化您的查询。像这样:

class FaultInstance(models.Model):
    auto = models.ForeignKey(Auto, related_name='fault_instances')
    fault = models.ForeignKey(Fault, related_name='fault_instances')
    ...

class Auto(models.Model):
    user = models.ForeignKey(AUTH_USER_MODEL, related_name='autos')

在这种情况下,您可以使用:

qs_faultinst = user.fault_instances.filter(fault__severity=severity).order_by('auto__make')

代替:

qs_faultinst = self.model.objects.select_related().filter(
                auto__user=user, fault__severity=severity
            ).order_by('auto__make')

我无法弄清楚你的汇总表,可能是你的意思:

Fault    Make|Model|Year    NumberOfAutosAffected

在这种情况下,您可以使用aggregation。但是,如果您有足够的数据,它(分组)仍然会很慢。一个简单的解决方案是通过创建额外的模型来非规范化数据并创建几个signals 来更新它,或者您可以使用缓存。

如果您有一组预定义的严重性,那么请考虑一下:

class Fault(models.Model):

    SEVERITY_LOW = 0
    SEVERITY_MIDDLE = 1
    SEVERITY_HIGH = 2
    ...
    SEVERITY_CHOICES = (
        (SEVERITY_LOW, 'Low'),
        (SEVERITY_MIDDLE, 'Middle'),
        (SEVERITY_HIGH, 'High'),
        ...
    )
    ...
    severity = models.PositiveSmallIntegerField(default=SEVERITY_LOW,
                                                choices=SEVERITY_CHOICES)
    ...

在您的模板中,您只需遍历 Fault.SEVERITY_CHOICES。

更新:

改变你的模型:

Аllocate 模型到一个单独的模型中:

class AutoModel(models.Model):
    name = models.CharField(max_length=255)

更改模型 Auto 的字段模型:

class Auto(models.Model):
    ...
    auto_model = models.ForeignKey(AutoModel, related_name='cars')
    ...

添加一个模型:

class MyDenormalizedModelForReport(models.Model):

    fault = models.ForeignKey(Fault, related_name='reports')
    auto_model = models.ForeignKey(AutoModel, related_name='reports')
    year = models.IntegerField(max_length=4)
    number_of_auto_affected = models.IntegerField(default=0)

添加一个信号:

def update_denormalized_model(sender, instance, created, **kwargs):
    if created:
        rep, dummy_created = MyDenormalizedModelForReport.objects.get_or_create(fault=instance.fault, auto_model=instance.auto.auto_model, year=instance.auto.year)
        rep.number_of_auto_affected += 1
        rep.save()

post_save.connect(update_denormalized_model, sender=FaultInstance)

【讨论】:

我添加了一些关于我的汇总表的更多信息。我喜欢你对我的严重程度的建议。但是,我不太了解您对数据非规范化和使用信号的建议。你能给我举一个更好的例子吗? 感谢安德烈的进一步解释。我将您的答案标记为正确,再次感谢!

以上是关于Django - 显示结果信息,同时使用具有多个外键关系的模型优化数据库查询的主要内容,如果未能解决你的问题,请参考以下文章

Django 和具有多个外键的模型

父模型具有多个外键时的Django外键反向访问[重复]

带有嵌套序列化程序的 Django 反向外键给出了多个结果

如何通过Django中的prefetch_related过滤具有多个条件的反向外键

显示具有多个需要累积的值的左连接

Django 表单:查询结果定义的字段,单个视图同时更新多个对象