在同一模型上应用union()不能识别使用GenericRelation的排序

时间:2019-10-05 06:46:34

标签: django django-orm

我有这样的Article模型

from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from hitcount.models import HitCountMixin, HitCount

class Article(models.Model):
    title = models.CharField(max_length=250)
    hit_count_generic = GenericRelation(
    HitCount, object_id_field='object_pk',
    related_query_name='hit_count_generic_relation')

当我做Article.objects.order_by('hit_count_generic__hits')时,我得到了结果。但是当我这样做时

articles_by_id = Article.objects.filter(id__in=ids).annotate(qs_order=models.Value(0, models.IntegerField()))
articles_by_name = Article.objects.filter(title__icontains='sports').annotate(qs_order=models.Value(1, models.IntegerField()))
articles = articles_by_id.union(articles_by_name).order_by('qs_order', 'hit_count_generic__hits')

获取错误

  

ORDER BY词与结果集中的任何列都不匹配

我如何实现这样的联合?我必须使用联合而不是AND和OR,因为我需要保留顺序。即article_by_id应该放在第一位,articles_by_name应该放在第二位。

使用Django命中数作为命中数https://github.com/thornomad/django-hitcount。命中数模型如下。

class HitCount(models.Model):
"""
Model that stores the hit totals for any content object.
"""
hits = models.PositiveIntegerField(default=0)
modified = models.DateTimeField(auto_now=True)
content_type = models.ForeignKey(
    ContentType, related_name="content_type_set_for_%(class)s", on_delete=models.CASCADE)
object_pk = models.TextField('object ID')
content_object = GenericForeignKey('content_type', 'object_pk')

objects = HitCountManager()

如@Angela所建议,尝试了与预取相关的操作。

articles_by_id = Article.objects.prefetch_related('hit_count_generic').filter(id__in=[1, 2, 3]).annotate(qs_order=models.Value(0, models.IntegerField()))
articles_by_name = Article.objects.prefetch_related('hit_count_generic').filter(title__icontains='date').annotate(qs_order=models.Value(1, models.IntegerField()))

检查时与prefetch_related相关的查询根本没有选择点击数。

 SELECT "articles_article"."id", "articles_article"."created", "articles_article"."last_changed_date", "articles_article"."title", "articles_article"."title_en", "articles_article"."slug", "articles_article"."status", "articles_article"."number_of_comments", "articles_article"."number_of_likes", "articles_article"."publish_date", "articles_article"."short_description", "articles_article"."description", "articles_article"."cover_image", "articles_article"."page_title", "articles_article"."category_id", "articles_article"."author_id", "articles_article"."creator_id", "articles_article"."article_type", 0 AS "qs_order" FROM "articles_article" WHERE "articles_article"."id" IN (1, 2, 3)

2 个答案:

答案 0 :(得分:0)

来自Django's official documentation

  

此外,数据库对组合查询中允许的操作设置了限制。例如,大多数数据库在组合查询中不允许LIMIT或OFFSET。

因此,请确保您的数据库允许组合这样的查询。

答案 1 :(得分:0)

  

ORDER BY词与结果集中的任何列都不匹配

您正在收到此错误,因为这正是发生的情况。您的 articles 最终结果集不包含 hitcount表中的hits列,因此,结果集无法使用此列进行排序。

在深入研究答案之前,让我们先了解一下django查询集的情况。

检索一组特定的文章,并包括一个额外的订购字段qs_order设置为0。

articles_by_id = Article.objects.filter(id__in=ids).annotate(qs_order=models.Value(0, models.IntegerField()))

针对上述内容的SQL查询

Select id, title,....., 0 as qs_order from article where article.id in (Select ....) # whatever you did to get your ids or just a flat list

检索另一组文章,并包括一个额外的订购字段qs_order设置为1

articles_by_name = Article.objects.filter(title__icontains='sports').annotate(qs_order=models.Value(1, models.IntegerField()))

针对上述内容的SQL查询

Select id, title, ...1 as qs_order from article where title ilike '%sports%'

原始查询集和order_by hit_count_generic__hits

Article.objects.order_by('hit_count_generic__hits')

这实际上将执行内部联接,并按hits列获取hitcount表以进行排序。

查询

Select id, title,... from article inner join hitcount on ... order by hits ASC

联盟

因此,当您进行并集时,将合并以上两个查询的结果集,然后使用您的 qs_order hits 对其进行排序...

解决方案

使用prefetch_related在初始查询集过滤中获取点击计数表,因此您可以使用联合中的hits列进行排序。

articles_by_id = Article.objects.prefetch_related('hit_count_generic').filter(id__in=ids).annotate(qs_order=models.Value(0, models.IntegerField()))
articles_by_name = Article.objects.prefetch_related('hit_count_generic').filter(title__icontains='sports').annotate(qs_order=models.Value(1, models.IntegerField()))

现在,由于在SELECT查询中都具有​​所需的表及其列,因此,合并将按照您定义的方式工作。

articles = articles_by_id.union(articles_by_name).order_by('qs_order', 'hit_count_generic__hits')