queryset - 如何在Django視圖中組合2個或更多的查詢集?




merge queryset django (8)

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

我知道這個問題有點老,但我會提出我的解決方案,以幫助某人。

如果你想結合查詢集並仍然使用QuerySet ,你可能需要檢查django-queryset-sequence

但有一個關於它的說明。 它只有兩個查詢集,因為它是參數。 但是使用python reduce可以將它應用於多個查詢集。

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

我正在嘗試構建搜索我正在構建的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


嘗試這個:

matches = pages | articles | posts

保留查詢集的所有功能,如果您想order_by或類似的話,這些功能都很好。

糟糕,請注意,這不適用於來自兩種不同模型的查詢集...


將查詢集連接到列表是最簡單的方法。 如果數據庫無論如何都會針對所有查詢集(例如,因為結果需要排序),這不會增加額外的成本。

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

您可以使用下面的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()

在你的例子中,用法是:

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的paginator matches

itertools模塊是在Python 2.3中引入的,所以它應該在Django運行的所有Python版本中可用。


當前方法的一大缺點是對於大型搜索結果集而言效率低下,因為每次都必須從數據庫中提取整個結果集,即使您只打算顯示一頁結果。

為了僅從數據庫中拉下實際需要的對象,必須在QuerySet上使用分頁,而不是在列表上使用分頁。 如果你這樣做,Django實際上在查詢執行之前切割QuerySet,所以SQL查詢將使用OFFSET和LIMIT來只獲取你實際顯示的記錄。 但是你不能這樣做,除非你可以以某種方式將你的搜索放入單個查詢中。

鑑於所有三種模型都有標題和正文字段,為什麼不使用模型繼承 ? 只要所有三個模型都從具有標題和正文的共同祖先繼承而來,並將搜索作為祖先模型中的單個查詢來執行。


相關的,用於混合來自同一模型的查詢集或來自少數模型的類似字段,從Django 1.11開始, qs.union()方法也可用:

union()

union(*other_qs, all=False)

Django 1.11新增功能 使用SQL的UNION運算符合併兩個或更多QuerySets的結果。 例如:

>>> qs1.union(qs2, qs3)

UNION運算符默認只選擇不同的值。 要允許重複值,請使用all = True參數。

即使參數是其他模型的QuerySets,union(),intersection()和difference()也會返回第一個QuerySet類型的模型實例。 只要SELECT列表在所有QuerySet中都是相同的(至少是類型,只要類型的順序相同,名稱無關緊要)就可以傳遞不同的模型。

另外,在結果QuerySet上只允許使用LIMIT,OFFSET和ORDER BY(即slicing和order_by())。 此外, 數據庫對組合查詢中允許的操作進行限制。 例如,大多數數據庫不允許組合查詢中的LIMIT或OFFSET。

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


這裡有一個想法......從這三個結果中每一個拉下一整頁結果,然後拋出20個最不有用的結果......這消除了大型查詢集,這樣你只犧牲一點點性能而不是很多






django-q