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

2024-06-26 17:59:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我当前在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

这将返回以下列表数据结构:

[
  {
    '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”属性根据“种类”的顺序为最新的Complimentgiver.name”执行annotation操作…即,最后一个给予“种类”独特等称赞的人

以下是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)

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

@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

Tags: nameselfbymodelscountamountpersonlast
1条回答
网友
1楼 · 发布于 2024-06-26 17:59:50

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

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=Subquery(
            get_user_model().objects.filter(
                giver__post_id=self.pk,
                giver__kind=OutRef('kind')
            ).values('name').order_by('giver__created')[:1]
        )
    )
)

话虽如此,正如评论中所讨论的,^{} [Django-doc]是“反向”关系的名称,因此是获得用户给予或收到的称赞的人。因此,最好将其重命名为:

class Compliment(TimeStampedModel):
    giver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        related_name='given_compliments',
    )
    receiver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        related_name='received_compliments',
    )
    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=Subquery(
            get_user_model().objects.filter(
                given_compliments__post_id=self.pk,
                given_compliments__kind=OutRef('kind')
            ).values('name').order_by('giver__created')[:1]
        )
    )
)

相关问题 更多 >