Django 复杂注解
Django complex annotation
先决条件:
- 查询集必须 return
Article
s
- 查询集必须 return 个唯一对象
- 不得使用命中数据库的 for 循环(意味着对 N 个对象进行 N 次查询以进行注释)
我的模特:
class Report(BaseModel):
ios_report = JSONField()
android_report = JSONField()
class Article(BaseModel):
internal_id = models.IntegerField(unique=True)
title = models.CharField(max_length=500)
short_title = models.CharField(max_length=500)
picture_url = models.URLField()
published_date = models.DateField()
clip_link = models.URLField()
reports = models.ManyToManyField(
"Report", through="ArticleInReport", related_name="articles"
)
class ArticleInReport(BaseModel):
article = models.ForeignKey("core.Article", on_delete=models.CASCADE, related_name='articleinreports')
report = models.ForeignKey("core.Report", on_delete=models.CASCADE, related_name='articleinreports')
ios_views = models.IntegerField()
android_views = models.IntegerField()
@property
def total_views(self):
return self.ios_views + self.android_views
一切都从一个按设定时间间隔创建的 Report
对象开始。该报告包含有关文章及其各自观点的数据。 Report
将通过 ArticleInReport
与 Article
建立关系,它包含导入报告时 Article
中的用户总数.
在我看来,我需要显示以下信息:
- 在过去 30 分钟内获得浏览量的所有文章。
- 每篇文章都注释了以下信息,这就是我遇到的问题:
If present, the number of views the Article
object had in the last Report
. If not present, 0.
我的views.py
文件:
reports_in_time_range = Report.objects.filter(created_date__range=[starting_range, right_now]).order_by('created_date')
last_report = reports_in_time_range.prefetch_related('articles').last()
unique_articles = Article.objects.filter(articleinreports__report__in=reports_in_time_range).distinct('id')
articles = Article.objects.filter(id__in=unique_articles).distinct('id').annotate(
total_views=Case(
When(id__in=last_report.articles.values_list('id', flat=True),
then=F('articleinreports__ios_views') + F('articleinreports__android_views')),
default=0, output_field=IntegerField(),
))
对我的思考过程的一些解释:首先,只给我在时间范围内(filter(id__in=unique_articles)
)出现在相关报告中的文章,return 只给我不同的文章。接下来,如果文章的 ID 出现在 last report 的文章列表中(当然是通过 ArticleInReport
),计算 iOS views + Android ArticleInReport
.
的观看次数
上面的注释对大多数 Article
都有效,但对其他注释却无缘无故地失败了。我尝试了很多不同的方法,但似乎总是得到错误的结果。
您的方法存在问题,您需要使用 IN
来匹配唯一的确切 ID 将 return 比预期的范围大,您可以直接使用反向名称来过滤文章对象,也过度使用 unique
articles_with_views_in_range = (
Article.objects
.annotate(
total_views=Case(
When(articleinreports__range=(start_range, end_range),
then=F('articleinreports__ios_views') + F('articleinreports__android_views')),
default=0, output_field=IntegerField(),
)
).filter(total_views__gt=0)
)
我可以看到 then=F('articleinreports__ios_views') + F('articleinreports__android_views')
的问题,因为它不知道要使用哪个 ArticleInReport...。因此它可能会为与每篇文章关联的每个 ArticleInReport 创建重复项。正如@daniherrera 所建议的,您可以首先获取所需的所有文章,然后从上一份报告中获取所有 ArticleInReport,这将是 3 个查询。然后你可以循环浏览文章,如果你有文章的 ArticleInReport,分配视图计数,如果没有 - 分配零。如果您不需要对 total_views
进行任何进一步的 sql 操作,这将起作用。您可能希望在循环之前构建一个 {Article.id: ArticleInReport} 的字典以便于查找。
另一种方法(如果您需要一些过滤或排序或其他)是使用上一份报告中 ArticleInReport 的 Subquery
为 Article 查询集添加 total_views
注释。然后,您可以使用 Coalesce
运算符在文章在上一个报告中没有收到任何视图时将 Null 替换为零。
P. S、我觉得prefetch_related('articles')
没用,反正你用values_list。
P. P. S 你也不需要对 unique_articles 和文章进行区分,因为 __in 查找已经产生了不同的结果
避免命中数据库非常重要,但不是以这个代价。在我看来,您应该将查询拆分为两个或多个查询。拆分查询将提高可读性,也可能提高性能(有时两个简单的查询比一个复杂的查询运行得更快)请记住,您拥有 dics、理解和 itertools 的所有功能来处理您的部分结果。
reports_in_time_range = ( Report
.objects
.filter(created_date__range=[starting_range, right_now])
.order_by('created_date'))
last_report = reports_in_time_range.prefetch_related('articles').last()
report_articles_ids = ( Article
.objects
.filter(articleinreports__report=last_report)
.values_list('id', flat=True)
.distinct())
report_articles = ( Article
.objects
.filter(id__in=report_articles_ids)
.annotate( total_views=Sum(
F('articleinreports__ios_views') +
F('articleinreports__android_views'),
output_field=IntegerField()
)))
other_articles = ( Article
.objects
.exclude(id__in=report_articles_ids)
.annotate( total_views=ExpressionWrapper(
Value(0),
output_field=IntegerField())
)))
articles = report_articles | other_articles
先决条件:
- 查询集必须 return
Article
s - 查询集必须 return 个唯一对象
- 不得使用命中数据库的 for 循环(意味着对 N 个对象进行 N 次查询以进行注释)
我的模特:
class Report(BaseModel):
ios_report = JSONField()
android_report = JSONField()
class Article(BaseModel):
internal_id = models.IntegerField(unique=True)
title = models.CharField(max_length=500)
short_title = models.CharField(max_length=500)
picture_url = models.URLField()
published_date = models.DateField()
clip_link = models.URLField()
reports = models.ManyToManyField(
"Report", through="ArticleInReport", related_name="articles"
)
class ArticleInReport(BaseModel):
article = models.ForeignKey("core.Article", on_delete=models.CASCADE, related_name='articleinreports')
report = models.ForeignKey("core.Report", on_delete=models.CASCADE, related_name='articleinreports')
ios_views = models.IntegerField()
android_views = models.IntegerField()
@property
def total_views(self):
return self.ios_views + self.android_views
一切都从一个按设定时间间隔创建的 Report
对象开始。该报告包含有关文章及其各自观点的数据。 Report
将通过 ArticleInReport
与 Article
建立关系,它包含导入报告时 Article
中的用户总数.
在我看来,我需要显示以下信息:
- 在过去 30 分钟内获得浏览量的所有文章。
- 每篇文章都注释了以下信息,这就是我遇到的问题:
If present, the number of views the
Article
object had in the lastReport
. If not present, 0.
我的views.py
文件:
reports_in_time_range = Report.objects.filter(created_date__range=[starting_range, right_now]).order_by('created_date')
last_report = reports_in_time_range.prefetch_related('articles').last()
unique_articles = Article.objects.filter(articleinreports__report__in=reports_in_time_range).distinct('id')
articles = Article.objects.filter(id__in=unique_articles).distinct('id').annotate(
total_views=Case(
When(id__in=last_report.articles.values_list('id', flat=True),
then=F('articleinreports__ios_views') + F('articleinreports__android_views')),
default=0, output_field=IntegerField(),
))
对我的思考过程的一些解释:首先,只给我在时间范围内(filter(id__in=unique_articles)
)出现在相关报告中的文章,return 只给我不同的文章。接下来,如果文章的 ID 出现在 last report 的文章列表中(当然是通过 ArticleInReport
),计算 iOS views + Android ArticleInReport
.
上面的注释对大多数 Article
都有效,但对其他注释却无缘无故地失败了。我尝试了很多不同的方法,但似乎总是得到错误的结果。
您的方法存在问题,您需要使用 IN
来匹配唯一的确切 ID 将 return 比预期的范围大,您可以直接使用反向名称来过滤文章对象,也过度使用 unique
articles_with_views_in_range = (
Article.objects
.annotate(
total_views=Case(
When(articleinreports__range=(start_range, end_range),
then=F('articleinreports__ios_views') + F('articleinreports__android_views')),
default=0, output_field=IntegerField(),
)
).filter(total_views__gt=0)
)
我可以看到 then=F('articleinreports__ios_views') + F('articleinreports__android_views')
的问题,因为它不知道要使用哪个 ArticleInReport...。因此它可能会为与每篇文章关联的每个 ArticleInReport 创建重复项。正如@daniherrera 所建议的,您可以首先获取所需的所有文章,然后从上一份报告中获取所有 ArticleInReport,这将是 3 个查询。然后你可以循环浏览文章,如果你有文章的 ArticleInReport,分配视图计数,如果没有 - 分配零。如果您不需要对 total_views
进行任何进一步的 sql 操作,这将起作用。您可能希望在循环之前构建一个 {Article.id: ArticleInReport} 的字典以便于查找。
另一种方法(如果您需要一些过滤或排序或其他)是使用上一份报告中 ArticleInReport 的 Subquery
为 Article 查询集添加 total_views
注释。然后,您可以使用 Coalesce
运算符在文章在上一个报告中没有收到任何视图时将 Null 替换为零。
P. S、我觉得prefetch_related('articles')
没用,反正你用values_list。
P. P. S 你也不需要对 unique_articles 和文章进行区分,因为 __in 查找已经产生了不同的结果
避免命中数据库非常重要,但不是以这个代价。在我看来,您应该将查询拆分为两个或多个查询。拆分查询将提高可读性,也可能提高性能(有时两个简单的查询比一个复杂的查询运行得更快)请记住,您拥有 dics、理解和 itertools 的所有功能来处理您的部分结果。
reports_in_time_range = ( Report
.objects
.filter(created_date__range=[starting_range, right_now])
.order_by('created_date'))
last_report = reports_in_time_range.prefetch_related('articles').last()
report_articles_ids = ( Article
.objects
.filter(articleinreports__report=last_report)
.values_list('id', flat=True)
.distinct())
report_articles = ( Article
.objects
.filter(id__in=report_articles_ids)
.annotate( total_views=Sum(
F('articleinreports__ios_views') +
F('articleinreports__android_views'),
output_field=IntegerField()
)))
other_articles = ( Article
.objects
.exclude(id__in=report_articles_ids)
.annotate( total_views=ExpressionWrapper(
Value(0),
output_field=IntegerField())
)))
articles = report_articles | other_articles