Django ORM 优化 - 按计数注释以及注释过滤器的最后一个条目

Django ORM Optimisation - annotating by count as well as the last entry for the annotate filter

我目前在 Post 型号上有以下 属性:

@property
def compliments(self):
    compliments_by_kind = list(
        self.compliment_set.values(
            'kind'
        ).annotate(
            amount=Count('kind')
        ).values(
            'kind',
            'amount'
        )
    )

    for compliment_by_kind in compliments_by_kind:
        compliment_by_kind['last_giver'] = self.compliment_set.filter(
            kind=compliment_by_kind['kind']
        ).order_by(
            'created'
        ).last().giver.name

    return compliments_by_kind

此returns以下列表数据结构:

[
  {
    'kind': 'unique', 
    'amount': 3, 
    'last_giver': 'Person 1'
  }, 
  {
    'kind': 'fresh', 
    'amount': 2, 
    'last_giver': 'Person 2'
  }, 
  {
    'kind': 'concept', 
    'amount': 3, 
    'last_giver': 'Person 3'
  }, 
  {
    'kind': 'lines', 
    'amount': 1, 
    'last_giver': 'Person 4'
  }
]

数据本身没有任何问题。在循环中执行查询的性能没有任何问题。

但是,循环方法 - 效率不高 - 对于每种类型(总共有 6 个,在一个之上还有 6 个进一步的查询来获取 Count 注释。所以,这真的阻碍了 serialization 舞台上的表现。

谁知道如何根据 created 属性的排序对最新的 Compliment "giver.name" 执行 annotation "kind" ... 即,最后一个称赞 "kind" 独特等

的人

这里是 Compliment 模型:

class Compliment(TimeStampedModel):
    giver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        related_name="giver",
    )
    receiver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        related_name="receiver",
    )
    post = models.ForeignKey('blog.post', on_delete=models.CASCADE)

    kind = models.CharField(choices=COMPLIMENTS_CHOICES, max_length=15)

更新:利用 Willem 的绝妙建议,我的解决方案是:

@property
def compliments(self):
    from django.contrib.auth import get_user_model
    from django.db.models import OuterRef, Subquery

    compliments_by_kind = list(
        self.compliment_set.values(
            'kind'
        ).annotate(
            amount=Count('kind'),
            first_name=Subquery(
                get_user_model().objects.filter(
                    giver__artwork_id=self.pk,
                    giver__kind=OuterRef('kind')
                ).values('first_name').order_by('-giver__created')[:1]
            ),
            last_name=Subquery(
                get_user_model().objects.filter(
                    giver__artwork_id=self.pk,
                    giver__kind=OuterRef('kind')
                ).values('last_name').order_by('-giver__created')[:1]
            ),
        )
    )

    return compliments_by_kind

您可以在同一查询中获得 last_giver(从而避免 N+1 问题),方法是使用 Subquery:

from django.contrib.auth import get_user_model
from django.db.models import OuterRef, Subquery

compliments_by_kind = list(
    self.compliment_set.values(
        'kind'
    ).annotate(
        amount=Count('kind')
        last_name=<b>Subquery(</b>
            get_user_model().objects.filter(
                giver__post_id=self.pk,
                <b>giver__kind=OutRef('kind')</b>
            ).values('name').order_by(<b>'giver__created'</b>)[:1]
        <b>)</b>
    )
)

话虽这么说,正如评论中所讨论的,related_name=… [Django-doc] 是 "reverse" 中关系的名称,因此获得用户已给予或收到的赞美的人。因此,最好将它们重命名为:

class Compliment(TimeStampedModel):
    giver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        <b>related_name='given_compliments'</b>,
    )
    receiver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        <b>related_name='received_compliments'</b>,
    )
    post = models.ForeignKey('blog.post', on_delete=models.CASCADE)
    kind = models.CharField(choices=COMPLIMENTS_CHOICES, max_length=15)

然后查询看起来像:

from django.contrib.auth import get_user_model
from django.db.models import OuterRef, Subquery

compliments_by_kind = list(
    self.compliment_set.values(
        'kind'
    ).annotate(
        amount=Count('kind')
        last_name=<b>Subquery(</b>
            get_user_model().objects.filter(
                <b>given_compliments__post_id</b>=self.pk,
                <b>given_compliments__kind=OutRef('kind')</b>
            ).values('name').order_by(<b>'giver__created'</b>)[:1]
        <b>)</b>
    )
)