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

问题描述 投票:613回答:11

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

我该怎么做?我已经试过了:

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_listarticle_listpost_list这三个列表?

django search django-queryset django-q
11个回答
1009
投票

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

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'))

6
投票

这是一个主意。。。只需要从三个结果中拉出一整页的结果,然后扔掉20个最不有用的结果...这就消除了大的查询集,因此您只牺牲了一点性能而不是很多


-1
投票

此递归函数将一组查询集串联为一个查询集。

from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))

435
投票

尝试一下:

matches = pages | articles | posts

它保留了查询集的所有功能,如果您想使用order_by或类似的功能,这很好。

请注意:不适用于来自两个不同模型的查询集。


102
投票

相关,用于混合来自同一模型的查询集,或用于某些模型的相似字段,从Django 1.11开始,qs.union() method也可用:

qs.union()

union()

Django 1.11的新功能。使用SQL的UNION运算符合并两个或多个QuerySet的结果。例如:

union(*other_qs, all=False)

UNION运算符默认情况下仅选择不同的值。要允许重复值,请使用all = True论点。

union(),intersection()和difference()返回以下模型的实例即使参数是的QuerySet,第一个QuerySet的类型其他型号。只要SELECT,传递不同的模型就可以所有QuerySet中的list都相同(至少类型,名称不一样)只要类型按相同顺序就可以)。

此外,仅LIMIT,OFFSET和ORDER BY(即切片和在结果QuerySet上允许使用order_by())。此外,数据库对合并中允许的操作进行限制查询。例如,大多数数据库不允许在LIMIT或OFFSET中组合查询。

>>> qs1.union(qs2, qs3)


75
投票

您可以使用下面的https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union类。当与Django的分页器一起使用时,它只应对所有查询集使用QuerySetChain查询命中数据库,而对于记录在当前页面上显示的那些查询集仅应对COUNT(*)查询命中数据库。

请注意,即使将SELECT()与通用视图一起使用,即使链接的查询集都使用相同的模型,也需要指定template_name=

QuerySetChain

在您的示例中,用法是:

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) 一样,将分页器与matches一起使用。

result_list模块是Python 2.3中引入的,因此在Django运行的所有Python版本中都应该可用。


27
投票

[当前方法的最大缺点是,它对于大型搜索结果集的效率低下,因为即使您只打算显示一页结果,您每次都必须从数据库中拉出整个结果集。

为了只从数据库中拉出您实际需要的对象,必须对QuerySet而不是列表使用分页。如果执行此操作,则Django实际上会在执行查询之前对QuerySet进行切片,因此SQL查询将使用OFFSET和LIMIT仅获取您实际显示的记录。但是,除非您可以以某种方式将搜索塞入单个查询中,否则您将无法执行此操作。

鉴于您的所有三个模型都有标题和正文字段,为什么不使用itertools?只需让这三个模型都继承自具有标题和正文的共同祖先,然后对祖先模型作为单个查询执行搜索。


22
投票

如果要链接很多查询集,请尝试以下操作:

model inheritance

其中:docs是查询集列表


16
投票
from itertools import chain
result = list(chain(*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


7
投票

要求:https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVwDjango==2.0.2

如果您想组合django-querysetsequence==0.8并且仍然带有querysets,则可能要签出QuerySet

但是有一点需要注意。它仅需两个django-queryset-sequence作为参数。但是,使用python querysets,您可以始终将其应用于多个reduce

queryset

就是这样。以下是我遇到的情况以及如何使用from functools import reduce from queryset_sequence import QuerySetSequence combined_queryset = reduce(QuerySetSequence, list_of_queryset) list comprehensionreduce

django-queryset-sequence

7
投票

这可以通过两种方法实现。

第一种方法

对查询集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}) 使用联合运算符以对两个查询集进行联合。如果两个查询集都属于同一模型/单个模型,则可以使用联合运算符组合查询集。

对于实例

|

第二种方法

实现两个查询集之间组合操作的另一种方法是使用itertools链函数。

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
© www.soinside.com 2019 - 2024. All rights reserved.