什么时候应该在 Django 中使用注解和模型方法?

When should I use annotations vs. model methods in Django?

我有一种情况需要向查询集中的对象添加额外的数据,我很难理解什么时候执行带有注释的查询真的比在模型上实现自定义方法更有益class.

作为一般示例:

这个:

 MyModel.objects
        .filter(
            some_field=some_value
        )
        .annotate(
            has_something=Exists(
                Something.objects.filter(
                    whatever=OuterRef('pk'),
                    start_date__gte=SIX_MONTHS_AGO
                )
            ),
            something_else= ... # Some complex query
        )

与此相比:

class MyModel(models.Model):
    some_field = CharField()

    def has_something(self):
        return (self.something_set
                    .filter(start_date__gte=SIX_MONTHS_AGO)
                    .count() > 0)

    def something_else(self):
        ... # Make some other queries


class Something(models.Model):
    whatever = models.ForeignKey(MyModel)
    start_date = models.DateField()

所以在这两种情况下,我都可以在任何需要的地方做这样的事情(例如,在模板中):

{% for obj in my_model_queryset %}
    {{ obj.has_something }}
{% endfor %}

据我所知,在进行注释时,所有工作都由数据库引擎完成,并且所有操作都可以在一次数据库命中中完成(我认为?)。另一方面,在模型 class 上实现方法时,如果您需要获取反向查找或来自外部模型的数据,则可能会执行其他查询。

我只想问一下,对于何时使用一种方法而不是另一种方法,是否有任何经验法则或你们都考虑的任何指导?特别是如果注释变得非常复杂(例如,使用 Q 对象、子查询、GroupConcat 等)

谢谢。

举个例子:

model_datas = Model.objects.filter(
    field_date__date__range=[date_from, date_to]
).order_by(
    'field_date'
)


# each line will perform a query here :(
for model in model_datas:
    writer.writerow([
        model.user.id,
        model.user.email,
        model.business_provider.encode('utf-8'),
        model.field_date.date()
    ])

这将为所有 Model 执行 1 次查询 + 1 次查询,并在 User 上为循环中的每个 model 执行连接

但是你可以在一个查询中完成:

csv_datas = Model.objects.filter(
    field_date__date__range=[date_from, date_to]
).order_by(
    'field_date'
).annotate(
    user_id=F('model__user_id'),
    user_email=F('model__user__email'),
    business_provider=F('model__provider_name'),
    field_date=F('model__field_date'),
).values(
    'user_id',
    'user_email',
    'business_provider',
    'field_date'
)

for data in csv_datas:
    writer.writerow([
        data["user_id"],
        data["user_email"],
        data["business_provider"].encode('utf-8'),
        data["field_date"].date()
    ])

这里只执行一个查询,就是要做的事情。

在 Django 中,从不执行带有子查询的循环,这对性能非常不利。

使用 FQ 你可以做非常复杂的事情,当它真的很复杂时我喜欢做的是拆分多个查询并只使用 :

query_1_ids = Model.objects.filter(XXXX).values_list('pk', flat=True)

query2 = Model2.objects.filter(model__pk__in=query_1_ids)

这是我的经验法则,但如果这不合适,我会执行一个 python 过程来聚合 setdict 我需要的东西(但这确实难得来这里)