如何在 Django 中组合多个 QuerySet?

2024-12-02 08:42:00
admin
原创
178
摘要:问题描述:我正在尝试为正在构建的 Django 网站构建搜索,在该搜索中,我搜索了三个不同的模型。为了在搜索结果列表中进行分页,我想使用通用的 object_list 视图来显示结果。但要做到这一点,我必须将三个 QuerySet 合并为一个。我该怎么做?我试过这个:result_list = [] page...

问题描述:

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

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

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


解决方案 1:

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

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))

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

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

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

您可以反转排序顺序:

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'),
    reverse=True,
)

attrgetter与以下内容等效lambda(这是 Python 2.4 之前必须完成的方式):

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=lambda instance: instance.date_created,
)

解决方案 2:

尝试一下:

matches = pages | articles | posts

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

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

解决方案 3:

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

union()

union(*other_qs, all=False)

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

>>> qs1.union(qs2, qs3)

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

union()、intersection() 和 difference() 返回第一个 QuerySet 类型的模型实例,即使参数是其他模型的 QuerySet。只要所有 QuerySet 中的 SELECT 列表相同(至少类型相同,名称无关紧要,只要类型顺序相同),传递不同的模型就可以。

此外,生成的 QuerySet 只允许 LIMIT、OFFSET 和 ORDER BY(即切片和 order_by())。此外,数据库对组合查询中允许的操作有所限制。例如,大多数数据库不允许在组合查询中使用 LIMIT 或 OFFSET。

解决方案 4:

您可以使用QuerySetChain下面的类。当与 Django 的分页器一起使用时,它应该只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

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

解决方案 5:

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

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

其中:docs 是查询集列表

解决方案 6:

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

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

鉴于您的三个模型都有 title 和 body 字段,为什么不使用模型继承呢?只需让所有三个模型都从具有 title 和 body 的共同祖先继承,然后在祖先模型上作为单个查询执行搜索即可。

解决方案 7:

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

第一种方法

使用 QuerySet 的 union 运算符|来合并两个 QuerySet。如果两个 QuerySet 属于同一模型/单个模型,则可以使用 union 运算符来合并 QuerySet。

举个例子

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

第二种方法

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

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

解决方案 8:

您可以使用Union:

qs = qs1.union(qs2, qs3)

但是如果您想应用于order_by组合查询集的外部模型......那么您需要以这种方式预先选择它们......否则它将不起作用。

例子

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

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

解决方案 9:

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。参见Alex Gaynor

解决方案 10:

要求:
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 comprehensionreduce`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})

解决方案 11:

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

解决方案 12:

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

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

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

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

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

解决方案 13:

这将无需使用任何其他库即可完成工作:

result_list = page_list | article_list | post_list

解决方案 14:

您可以使用“|”(按位或)来组合同一模型的查询集,如下所示:

# "store/views.py"

from .models import Food
from django.http import HttpResponse
                                                
def test(request):
                                             # ↓ Bitwise or
    result = Food.objects.filter(name='Apple') | Food.objects.filter(name='Orange')
    print(result)
    return HttpResponse("Test")

控制台上的输出:

<QuerySet [<Food: Apple>, <Food: Orange>]>
[22/Jan/2023 12:51:44] "GET /store/test/ HTTP/1.1" 200 9

并且,您可以使用|=添加相同模型的查询集,如下所示:

# "store/views.py"

from .models import Food
from django.http import HttpResponse
                                                
def test(request):
    result = Food.objects.filter(name='Apple')
         # ↓↓ Here
    result |= Food.objects.filter(name='Orange')
    print(result)
    return HttpResponse("Test")

控制台上的输出:

<QuerySet [<Food: Apple>, <Food: Orange>]>
[22/Jan/2023 12:51:44] "GET /store/test/ HTTP/1.1" 200 9

请注意,如果添加不同模型的查询集,如下所示:

# "store/views.py"

from .models import Food, Drink
from django.http import HttpResponse
                                                
def test(request):
          # "Food" model                      # "Drink" model
    result = Food.objects.filter(name='Apple') | Drink.objects.filter(name='Milk')
    print(result)
    return HttpResponse("Test")

下面有一个错误:

AssertionError: Cannot combine queries on two different base models.
[22/Jan/2023 13:40:54] "GET /store/test/ HTTP/1.1" 500 96025

但是,如果添加不同模型的空查询集,如下所示:

# "store/views.py"

from .models import Food, Drink
from django.http import HttpResponse
                                                
def test(request):
          # "Food" model                       # Empty queryset of "Drink" model 
    result = Food.objects.filter(name='Apple') | Drink.objects.none()
    print(result)
    return HttpResponse("Test")

下面没有错误:

<QuerySet [<Food: Apple>]>
[22/Jan/2023 13:51:09] "GET /store/test/ HTTP/1.1" 200 9

再次要小心,如果通过get()添加对象,如下所示:

# "store/views.py"

from .models import Food
from django.http import HttpResponse
                                                
def test(request):
    result = Food.objects.filter(name='Apple')
                         # ↓↓ Object
    result |= Food.objects.get(name='Orange')
    print(result)
    return HttpResponse("Test")

下面有一个错误:

AttributeError: 'Food' object has no attribute '_known_related_objects'
[22/Jan/2023 13:55:57] "GET /store/test/ HTTP/1.1" 500 95748

解决方案 15:

要获取两个查询集的交集

result = first_queryset.intersection(second_queryset)

解决方案 16:

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

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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用