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>
)
)
我目前在 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>
)
)