ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我怎样才能做到这一点?我试过这个:

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

看起来 t_rybik 在 djangosnippets.org/snippets/1933 创建了一个全面的解决方案
对于搜索,最好使用像 Haystack 这样的专用解决方案 - 它非常灵活。
Django 用户 1.11 和 abv,请参阅此答案 - stackoverflow.com/a/42186970/6003362
注意:问题仅限于在将 3 个不同的模型合并在一起后不需要再次提取列表中的模型来区分类型数据的极少数情况。在大多数情况下——如果需要区分——它会出现错误的界面。对于相同型号:请参阅有关 union 的答案。

a
akaihola

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

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

如果合并来自同一个表的查询集以执行 OR 查询,并且有重复的行,您可以使用 groupby 函数消除它们:from itertools import groupby unique_results = [rows.next() for (key, rows) in groupby(result_list, key=lambda obj: obj.id)]
好的,关于这个上下文中的 groupby 函数。使用 Q 函数,您应该能够执行所需的任何 OR 查询:https://docs.djangoproject.com/en/1.3/topics/db/queries/#complex-lookups-with-q-objects
@apelliciari Chain 使用的内存比 list.extend 少得多,因为它不需要将两个列表都完全加载到内存中。
@AWrightIV 这是该链接的新版本:docs.djangoproject.com/en/1.8/topics/db/queries/…
尝试这种方法但有 'list' object has no attribute 'complex_filter'
D
Daniel Holmes

尝试这个:

matches = pages | articles | posts

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

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


但是,不适用于切片查询集。还是我错过了什么?
我曾经使用“|”加入查询集但并不总是正常工作。最好使用“Q”:docs.djangoproject.com/en/dev/topics/db/queries/…
这里 | 是集合并集运算符,而不是按位或。
@e100 不,它不是集合联合运算符。 django 重载按位或运算符:github.com/django/django/blob/master/django/db/models/…
请注意,此解决方案保留顺序,因此无论您使用 s1 | s2 还是 s2 | s1,集合 {x,y,x} 和集合 {a,b,c} 都可能以 {a,b,c,x,y,z} 结束,这使得 { 6} 在很多情况下有点没用。
P
Peter Mortensen

相关,对于来自同一模型的混合查询集,或对于来自几个模型的类似字段,开始 Django 1.11 也可以使用 QuerySet.union() method

union() union(*other_qs, all=False) Django 1.11 中的新功能。使用 SQL 的 UNION 运算符组合两个或多个 QuerySet 的结果。例如: >>> qs1.union(qs2, qs3) UNION 运算符默认只选择不同的值。要允许重复值,请使用 all=True 参数。 union()、intersection() 和 difference() 返回第一个 QuerySet 类型的模型实例,即使参数是其他模型的 QuerySet。只要 SELECT 列表在所有 QuerySets 中都相同,传递不同的模型就可以工作(至少类型,名称无关紧要,只要类型顺序相同)。此外,生成的 QuerySet 上只允许使用 LIMIT、OFFSET 和 ORDER BY(即切片和 order_by())。此外,数据库对组合查询中允许的操作设置了限制。例如,大多数数据库不允许在组合查询中使用 LIMIT 或 OFFSET。


对于我需要具有唯一值的问题集,这是一个更好的解决方案。
你从哪里进口工会?它是否必须来自 X 个查询集之一?
是的,它是一种查询集的方法。
我认为它删除了搜索过滤器
请记住,您在使用 union()将无法filter()此查询集。 filter() 只会静默失败。至少在 Django 2.2 中
a
akaihola

您可以使用下面的 QuerySetChain 类。将它与 Django 的分页器一起使用时,它应该只对所有查询集的 COUNT(*) 查询和 SELECT() 查询只针对其记录显示在当前页面上的那些查询集来访问数据库。

请注意,如果使用带有通用视图的 QuerySetChain,您需要指定 template_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)

然后将 matches 与分页器一起使用,就像您在示例中使用的 result_list 一样。

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


不错的方法,但我在这里看到的一个问题是查询集是“从头到尾”附加的。如果每个查询集都按日期排序并且需要组合集也按日期排序怎么办?
这当然看起来很有希望,太好了,我必须尝试一下,但我今天没有时间。如果它解决了我的问题,我会回复你。做得好。
好的,我今天必须尝试,但它没有用,首先它抱怨它不需要 _clone 属性所以我添加了那个,只是复制了 _all 并且有效,但似乎分页器对这个查询集有一些问题。我收到此分页器错误:“len() of unsized object”
@Espen Python 库:pdb,日志记录。外部:IPython、ipdb、django-logging、django-debug-toolbar、django-command-extensions、werkzeug。在代码中使用打印语句或使用日志记录模块。最重要的是,学会在 shell 中进行自省。谷歌关于调试 Django 的博客文章。乐意效劳!
@patrick 请参阅 djangosnippets.org/snippets/1103djangosnippets.org/snippets/1933 - 特别是后者是一个非常全面的解决方案
v
vutran

如果你想链接很多查询集,试试这个:

from itertools import chain
result = list(chain(*docs))

其中: docs 是查询集列表


C
Carl Meyer

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

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

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


D
Devang Padhiyar

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

第一种方法

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

举个例子

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

第二种方法

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

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

代替 itertools.chain(分别运行每个查询),functools.reduce(operator.or_, [pagelist1, pagelist2]) 可用于以编程方式应用您的第一种方法。这会产生一个查询。
P
Peter Mortensen

您可以使用 Union

qs = qs1.union(qs2, qs3)

但是,如果您想在组合查询集的外部模型上应用 order_by...那么您需要以这种方式预先Select它们...否则它将无法正常工作。

例子

qs = qs1.union(qs2.select_related("foreignModel"), qs3.select_related("foreignModel"))
qs.order_by("foreignModel__prop1")

其中 prop1 是外部模型中的一个属性。


r
ray6080
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)

引自https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw。见 亚历克斯·盖纳


S
Stephen Rauch

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

如果您想组合 querysets 并仍然得出 QuerySet,您可能需要查看 django-queryset-sequence

但是关于它的一个注释。它只需要两个 querysets 作为参数。但是使用 python reduce,您始终可以将其应用于多个 queryset

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

就是这样。以下是我遇到的情况以及我如何使用 list comprehensionreducedjango-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})

Book.objects.filter(owner__mentor=mentor) 不做同样的事情吗?我不确定这是一个有效的用例。我认为 Book 可能需要多个 owner 才能开始执行此类操作。
是的,它做同样的事情。我尝试过这个。无论如何,也许这在其他情况下可能有用。感谢您指出了这一点。作为初学者,您一开始并不完全了解所有快捷方式。有时你必须走在负载蜿蜒的道路上才能欣赏乌鸦的飞行
P
Peter Mortensen

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


P
Peter Mortensen

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

result_list = page_list | article_list | post_list

值得注意的是,这可能不会保留您的结果的顺序
P
Peter Mortensen

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

# Union method
result_list = page_list.union(article_list, post_list)

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

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

# intersection method
result_list = page_list.intersection(article_list, post_list)

P
Petr Dvořáček

这个递归函数将一组查询集连接成一个查询集。

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

我真的迷路了。
我们结合查询结果,它不能在运行时使用,这样做真的很糟糕。因为有时它会在结果上添加重复。
太复杂了,递归会导致服务器消耗资源。