如何在Django视图中组合两个或多个查询集?

如何在Django视图中组合两个或多个查询集?,django,search,django-queryset,django-q,Django,Search,Django Queryset,Django Q,我正在尝试建立一个Django网站的搜索,在这个搜索中,我搜索了三种不同的模型。为了对搜索结果列表进行分页,我想使用通用对象列表视图来显示结果。但要做到这一点,我必须将三个查询集合并为一个查询集 我该怎么做?我试过这个: result_list = [] page_list = Page.objects.filter( Q(title__icontains=cleaned_search_term) | Q(body__icontains=cleaned_search_term))

我正在尝试建立一个Django网站的搜索,在这个搜索中,我搜索了三种不同的模型。为了对搜索结果列表进行分页,我想使用通用对象列表视图来显示结果。但要做到这一点,我必须将三个查询集合并为一个查询集

我该怎么做?我试过这个:

result_list = []
page_list = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term))
article_list = Article.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term) |
    Q(tags__icontains=cleaned_search_term))
post_list = Post.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term) |
    Q(tags__icontains=cleaned_search_term))

for x in page_list:
    result_list.append(x)
for x in article_list:
    result_list.append(x)
for x in post_list:
    result_list.append(x)

return object_list(
    request,
    queryset=result_list,
    template_object_name='result',
    paginate_by=10,
    extra_context={
        'search_term': search_term},
    template_name="search/result_list.html")
但这不起作用。当我尝试在通用视图中使用该列表时,我会出错。列表缺少克隆属性


如何合并这三个列表,
page\u list
article\u list
post\u list

当前方法的最大缺点是搜索结果集太大,效率低下,因为每次都必须从数据库中提取整个结果集,即使您只想显示一页结果

为了只从数据库中提取实际需要的对象,必须对查询集而不是列表使用分页。如果这样做,Django实际上会在执行查询之前对查询集进行切片,因此SQL查询将使用OFFSET和LIMIT来仅获取实际显示的记录。但是你不能这样做,除非你能把你的搜索塞进一个查询中


既然三个模型都有title和body字段,为什么不使用?只需让所有三个模型都继承自一个具有标题和正文的共同祖先,并在祖先模型上作为单个查询执行搜索。

您可以使用下面的
QuerySetChain
类。当它与Django的paginator一起使用时,它应该只对所有查询集执行
COUNT(*)
查询,并且只对记录显示在当前页面上的查询集执行
SELECT()
查询

请注意,如果对泛型视图使用
查询链
,则需要指定
template\u name=
,即使链接查询集都使用相同的模型

from itertools import islice, chain

class QuerySetChain(object):
    """
    Chains multiple subquerysets (possibly of different models) and behaves as
    one queryset.  Supports minimal methods needed for use with
    django.core.paginator.
    """

    def __init__(self, *subquerysets):
        self.querysets = subquerysets

    def count(self):
        """
        Performs a .count() for all subquerysets and returns the number of
        records as an integer.
        """
        return sum(qs.count() for qs in self.querysets)

    def _clone(self):
        "Returns a clone of this queryset chain"
        return self.__class__(*self.querysets)

    def _all(self):
        "Iterates records in all subquerysets"
        return chain(*self.querysets)

    def __getitem__(self, ndx):
        """
        Retrieves an item or slice from the chained set of results from all
        subquerysets.
        """
        if type(ndx) is slice:
            return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1))
        else:
            return islice(self._all(), ndx, ndx+1).next()
在您的示例中,用法是:

pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term))
articles = Article.objects.filter(Q(title__icontains=cleaned_search_term) |
                                  Q(body__icontains=cleaned_search_term) |
                                  Q(tags__icontains=cleaned_search_term))
posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term) | 
                            Q(tags__icontains=cleaned_search_term))
matches = QuerySetChain(pages, articles, posts)
然后使用
匹配
与您在示例中使用的
结果列表
一样的分页器


Python2.3中引入了
itertools
模块,因此它应该在Django运行的所有Python版本中都可用。

将查询集连接到列表中是最简单的方法。如果所有查询集都会命中数据库(例如,因为需要对结果进行排序),这不会增加进一步的成本

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
使用
itertools.chain
比循环每个列表并逐个追加元素更快,因为
itertools
是用C实现的。它也比在连接之前将每个查询集转换为列表消耗更少的内存

现在可以对结果列表进行排序,例如按日期排序(根据hasen j对另一个答案的评论中的要求)。
sorted()
函数方便地接受生成器并返回列表:

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=lambda instance: instance.date_created)
如果您使用的是Python 2.4或更高版本,那么可以使用
attrgetter
而不是lambda。我记得读到过关于它速度更快的文章,但我没有看到百万物品列表中有明显的速度差异

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))

这里有一个想法。。。只需从三个结果中各取下一整页,然后扔掉20个最不有用的结果。。。这就消除了大型查询集,这样您只需牺牲一点性能,而不是很多性能。

尝试以下方法:

matches = pages | articles | posts
它保留了查询集的所有功能,如果您想按或类似方式对u进行排序,这很好


请注意:这不适用于两种不同型号的查询集。

如果要链接大量查询集,请尝试以下方法:

from itertools import chain
result = list(chain(*docs))
其中:docs是查询集的列表

DATE_FIELD_MAPPING = {
    Model1: 'date',
    Model2: 'pubdate',
}

def my_key_func(obj):
    return getattr(obj, DATE_FIELD_MAPPING[type(obj)])

And then sorted(chain(Model1.objects.all(), Model2.objects.all()), key=my_key_func)

引自。请参见Alex Gaynor

相关内容,了解来自同一型号的混合查询集,或来自少数型号的类似字段,也可从Django 1.11开始使用:

union()

union(*other_qs, all=False)
Django 1.11中新增了。使用SQL的UNION运算符组合两个或多个查询集的结果。例如:

>>> qs1.union(qs2, qs3)
默认情况下,UNION运算符仅选择不同的值。要允许重复值,请使用all=True 争论

union()、intersection()和difference()返回的模型实例 第一个查询集的类型,即使参数是 其他型号。只要选择 列表在所有查询集中都是相同的(至少是类型,名称不相同) 只要类型的顺序相同,就有关系)

此外,仅限限位、偏移和按顺序(即切片和 结果查询集上允许使用order_by()。此外,数据库 对组合中允许的操作设置限制 查询。例如,大多数数据库不允许在 组合查询


要求:
Django==2.0.2
Django querysetsequence==0.8

如果您想要组合
查询集
,但仍然得到一个
查询集
,您可能需要签出

但有一点值得注意。它只接受两个
queryset
作为参数。但是使用python
reduce
时,您总是可以将它应用于多个
queryset
s

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)
就这样。下面是我遇到的一个情况,以及我是如何使用
列表理解
减少
django queryset序列

from functools import reduce
from django.shortcuts import render    
from queryset_sequence import QuerySetSequence

class People(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    mentor = models.ForeignKey('self', null=True, on_delete=models.SET_NULL, related_name='my_mentees')

class Book(models.Model):
    name = models.CharField(max_length=20)
    owner = models.ForeignKey(Student, on_delete=models.CASCADE)

# as a mentor, I want to see all the books owned by all my mentees in one view.
def mentee_books(request):
    template = "my_mentee_books.html"
    mentor = People.objects.get(user=request.user)
    my_mentees = mentor.my_mentees.all() # returns QuerySet of all my mentees
    mentee_books = reduce(QuerySetSequence, [each.book_set.all() for each in my_mentees])

    return render(request, template, {'mentee_books' : mentee_books})

这个递归函数将queryset数组连接成一个queryset

def merge_query(ar):
    if len(ar) ==0:
        return [ar]
    while len(ar)>1:
        tmp=ar[0] | ar[1]
        ar[0]=tmp
        ar.pop(1)
        return ar

这可以通过两种方式实现

第一种方法

使用查询集的并集运算符
|
对两个查询集进行并集。如果两个查询集都属于同一个模型/单个模型,则可以使用union运算符组合查询集

例如

第二种方法

实现联合收割机操作的另一种方法
pagelist1 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
pagelist2 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
combined_list = pagelist1 | pagelist2 # this would take union of two querysets
from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))
result_list = page_list | article_list | post_list
qs = qs1.union(qs2, qs3)
qs = qs1.union(qs2.select_related("foreignModel"), qs3.select_related("foreignModel"))
qs.order_by("foreignModel__prop1")