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

esp*_*akk 613 django search django-queryset django-q

我正在尝试构建我正在构建的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")
Run Code Online (Sandbox Code Playgroud)

但这不起作用当我尝试在通用视图中使用该列表时,我收到错误.该列表缺少clone属性.

有人知道如何合并三个列表page_list,article_listpost_list

aka*_*ola 1012

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

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
Run Code Online (Sandbox Code Playgroud)

使用itertools.chain比循环每个列表并逐个附加元素更快,因为itertools在C中实现.它还比在连接之前将每个查询集转换为列表消耗更少的内存.

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

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=lambda instance: instance.date_created)
Run Code Online (Sandbox Code Playgroud)

如果您使用的是Python 2.4或更高版本,则可以使用attrgetter而不是lambda.我记得读到它的速度更快,但我没有看到一百万个项目列表的显着速度差异.

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))
Run Code Online (Sandbox Code Playgroud)

  • 通过投票来推动:今天发现这篇文章对我来说非常有用.谢谢! (103认同)
  • 如果合并来自同一个表的查询集以执行OR查询,并且具有重复的行,则可以使用groupby函数消除它们:`from itertools import groupby``numply_results = [rows.next()for(key,rows)in groupby( result_list,key = lambda obj:obj.id)]` (11认同)
  • 好的,关于此上下文中的 groupby 函数的 nm。使用 Q 函数,您应该能够执行您需要的任何 OR 查询:[https://docs.djangoproject.com/en/1.3/topics/db/queries/#complex-lookups-with-q-objects]( https://docs.djangoproject.com/en/1.3/topics/db/queries/#complex-lookups-with-q-objects) (2认同)
  • @apelliciari Chain使用的内存明显少于list.extend,因为它不需要将两个列表完全加载到内存中. (2认同)
  • @AWrightIV这是该链接的新版本:https://docs.djangoproject.com/en/1.8/topics/db/queries/#complex-lookups-with-q-objects (2认同)

小智 437

试试这个:

matches = pages | articles | posts
Run Code Online (Sandbox Code Playgroud)

保留查询集的所有功能,如果你想要order_by或类似的话,这是很好的.

糟糕,请注意,这不适用于来自两个不同模型的查询集...

  • 这里`|`是set union运算符,而不是按位OR. (14认同)
  • 但是,对切片查询集不起作用.或者我错过了什么? (9认同)
  • @ e100不,它不是set union运算符.django重载按位OR运算符:https://github.com/django/django/blob/master/django/db/models/query.py#L308 (4认同)
  • 请注意,此解决方案*不* 保留顺序,因此集合`{x,y,x}` 和集合`{a,b,c}` 可能以`{a,b,c,x,y, z}` 不管你是否使用 `s1 | s2` 或 `s2 | s1` 并且这使得 `|` 在很多情况下有点无用。 (3认同)
  • 我曾经使用“|”加入查询集 但并不总是工作正常。最好使用“Q”:https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q (2认同)

Udi*_*Udi 103

相关,对于混合来自相同模型的查询集,或来自几个模型的类似字段,从Django 1.11开始,还提供了一种qs.union()方法:

union()

union(*other_qs, all=False)
Run Code Online (Sandbox Code Playgroud)

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

>>> qs1.union(qs2, qs3)
Run Code Online (Sandbox Code Playgroud)

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

union(),intersection()和difference()返回第一个QuerySet类型的模型实例,即使参数是其他模型的QuerySets.只要所有QuerySet中的SELECT列表相同,传递不同的模型就会起作用(至少类型,只要类型相同,名称无关紧要).

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

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union

  • 请记住,使用“union()”后,您**将无法**再“filter()”此查询集。`filter()` 只会**默默地失败**。至少在 Django 2.2 中 (9认同)

aka*_*ola 75

您可以使用QuerySetChain下面的课程.当它与Django的paginator一起使用时,它应该仅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()
Run Code Online (Sandbox Code Playgroud)

在您的示例中,用法将是:

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)
Run Code Online (Sandbox Code Playgroud)

然后使用matchesresult_list在示例中使用的paginator .

itertools模块是在Python 2.3中引入的,因此它应该可以在Django运行的所有Python版本中使用.

  • 不错的方法,但我在这里看到的一个问题是查询集是"头对尾"附加的.如果每个查询集按日期排序并且需要组合集也按日期排序,该怎么办? (5认同)
  • @patrick见http://djangosnippets.org/snippets/1103/和http://djangosnippets.org/snippets/1933/ - 特别是后者是一个非常全面的解决方案 (4认同)

Car*_*yer 27

当前方法的一大缺点是它具有大量搜索结果集的低效率,因为每次必须从数据库中下拉整个结果集,即使您只打算显示一页结果.

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

鉴于您的所有三个模型都有标题和正文字段,为什么不使用模型继承?让所有三个模型继承自具有标题和正文的共同祖先,并在祖先模型上作为单个查询执行搜索.


vut*_*ran 22

如果您想链接大量查询集,请尝试以下操作:

from itertools import chain
result = list(chain(*docs))
Run Code Online (Sandbox Code Playgroud)

其中:docs是查询集的列表


ray*_*080 16

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)
Run Code Online (Sandbox Code Playgroud)

引用自https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw.见Alex Gaynor


Vig*_* Sk 13

您可以使用联盟

qs = qs1.union(qs2, qs3)
Run Code Online (Sandbox Code Playgroud)

但是如果你想应用order_by在组合查询集的外部模型上......那么你需要提前选择它们......否则它将不起作用。

例子

qs = qs1.union(qs2.select_related("foreignModel"), qs3.select_related("foreignModel"))
qs.order_by("foreignModel__prop1")
Run Code Online (Sandbox Code Playgroud)

prop1外国模型中的属性在哪里。


chi*_*imo 7

要求: Django==2.0.2,django-querysetsequence==0.8

如果你想要结合querysets并仍然出来QuerySet,你可能想看看django-queryset-sequence.

但有一点关于它.它只需要两个querysets作为参数.但是使用python,reduce你总是可以将它应用于多个querysets.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)
Run Code Online (Sandbox Code Playgroud)

就是这样.下面是我遇到了一个情况,我该如何使用list comprehension,reduce以及django-queryset-sequence

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})
Run Code Online (Sandbox Code Playgroud)

  • `Book.objects.filter(owner__mentor=mentor)` 不做同样的事情吗?我不确定这是一个有效的用例。我认为在你需要开始做这样的事情之前,一本书可能需要有多个“所有者”。 (2认同)

Dev*_*yar 7

这可以通过两种方式来实现。

第一种方法

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

对于一个实例

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
Run Code Online (Sandbox Code Playgroud)

第二种方法

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

from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))
Run Code Online (Sandbox Code Playgroud)

  • 可以使用“functools.reduce(operator.or_, [pagelist1, pagelist2])”以编程方式应用第一种方法,而不是“itertools.chain”(单独运行每个查询)。这会产生单个查询。 (4认同)

Jia*_*aro 6

这是一个想法...只需从三个中的每一个中拉下一整页结果,然后抛出20个最不实用的结果......这样就消除了大型查询集,这样你只会牺牲一点性能而不是很多


Dan*_*iaz 6

最好的选择是使用 Django 内置方法:

# Union method
result_list = page_list.union(article_list, post_list)
Run Code Online (Sandbox Code Playgroud)

这将返回这些查询集中所有对象的并集。

如果您只想获取三个查询集中的对象,您会喜欢查询集的内置方法intersection.

# intersection method
result_list = page_list.intersection(article_list, post_list)
Run Code Online (Sandbox Code Playgroud)


小智 5

这将在不使用任何其他库的情况下完成工作:

result_list = page_list | article_list | post_list
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,这可能不会保留结果的顺序 (3认同)