itsource

Django에서 여러 쿼리 세트를 결합하는 방법은 무엇입니까?

mycopycode 2023. 4. 22. 09:28
반응형

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

하지만 이건 안 돼일반 보기에서 해당 목록을 사용하려고 하면 오류가 발생합니다.목록에 복제 특성이 없습니다.

는 어떻게 Marge 할 수 요?page_list,article_list ★★★★★★★★★★★★★★★★★」post_list

쿼리 세트를 목록으로 연결하는 것이 가장 간단한 방법입니다.결과 정렬이 필요하기 때문에 데이터베이스가 모든 쿼리셋에 대해 히트하는 경우 추가 비용은 발생하지 않습니다.

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

「」를 사용합니다.itertools.chain각 목록을 루프하고 요소를 하나씩 추가하는 것보다 빠릅니다.itertools다고 하다또한 연결하기 전에 각 쿼리 세트를 목록으로 변환하는 것보다 메모리 사용량이 적습니다.

이제 결과 목록을 날짜별로 정렬할 수 있습니다(다른 답변에 대한 hasen j의 코멘트 요청).sorted()함수는 생성기를 편리하게 수신하고 목록을 반환합니다.

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

2. Python 2.4를 사용할 수 .attrgetter람다 대신.더 빠르다고 읽었던 건 기억하지만, 100만 아이템 리스트의 속도 차이는 눈에 띄지 않았습니다.

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

이것을 시험해 보세요.

matches = pages | articles | posts

되므로 쿼리셋의 기능을 할 수 .order_by또는 이와 유사합니다.

주의: 두 가지 다른 모델의 쿼리 세트에서는 작동하지 않습니다.

이와 관련하여 동일한 모델의 쿼리 세트를 혼합하거나 일부 모델의 유사한 필드에 대해 Django 1.11부터 다음 방법을 사용할 수 있습니다.

union()

union(*other_qs, all=False)

장고 1.11의 신기능.SQL의 UNION 연산자를 사용하여 두 개 이상의 QuerySet 결과를 결합합니다.예를 들어 다음과 같습니다.

>>> qs1.union(qs2, qs3)

UNION 연산자는 기본적으로 고유한 값만 선택합니다.중복된 값을 허용하려면 all=True 인수를 사용합니다.

union(), intersection() 및 difference()는 인수가 다른 모델의 QuerySet인 경우에도 첫 번째 QuerySet 유형의 모델인스턴스를 반환합니다.SELECT 목록이 모든 QuerySet에서 동일한 경우 다른 모델을 전달하면 됩니다(적어도 유형이 같은 경우 이름은 중요하지 않습니다).

또한 결과 QuerySet에서는 LIMIT, OFFSET 및 ORDER BY(슬라이싱 및 OFRSER_by())만 허용됩니다.또한 데이터베이스는 결합된 쿼리에서 허용되는 작업에 제한을 둡니다.예를 들어, 대부분의 데이터베이스는 결합된 쿼리에서 LIMIT 또는 OFFSET을 허용하지 않습니다.

.QuerySetChain와 함께 장고의 페이지미네이터와 함께 사용장고와 함께 데이터베이스만 쳐야 합니다.COUNT(*) 및 queryset에 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 버전에서 사용할 수 있습니다.

많은 쿼리 세트를 체인으로 하는 경우는, 다음과 같이 시험해 주세요.

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

여기서: docs는 쿼리 세트의 목록입니다.

현재 접근 방식의 가장 큰 단점은 검색 결과 세트가 크면 비효율적이라는 점입니다. 검색 결과 세트를 한 페이지만 표시하려고 해도 매번 전체 결과 세트를 데이터베이스에서 풀다운해야 하기 때문입니다.

데이터베이스에서 실제로 필요한 개체만 풀다운하려면 목록이 아닌 QuerySet에서 페이지 매김을 사용해야 합니다.이렇게 하면 Django는 쿼리가 실행되기 전에 실제로 QuerySet을 슬라이스하기 때문에 SQL 쿼리는 OFFSET과 LIMIT을 사용하여 실제로 표시되는 레코드만 가져옵니다.그러나 검색어를 하나의 쿼리에 삽입할 수 없으면 이 작업을 수행할 수 없습니다.

세 모델 모두에 제목과 본문 필드가 있으므로 모델 상속을 사용하는 것이 어떨까요?제목과 본문을 가진 공통 조상으로부터 세 가지 모델이 모두 상속되도록 하고 상위 모델에 대해 단일 쿼리로 검색을 수행합니다.

이것은 두 가지 방법으로도 달성될 수 있다.

첫 번째 방법

합니다.|두 쿼리셋을 결합할 수 있습니다.두 쿼리 집합이 동일한 모델/단일 모델에 속하는 경우 결합 연산자를 사용하여 쿼리 집합을 결합할 수 있습니다.

예를 들면

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

두 번째 방법

두 쿼리 세트 간에 결합 연산을 수행하는 다른 방법은 반복 도구 체인 함수를 사용하는 것입니다.

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

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는 외부 모델의 속성입니다.

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에서 인용했습니다.'알렉스 가이너'

요건: Django==2.0.2,django-querysetsequence==0.8

결합하고 싶은 경우querysets그리고 아직까지도QuerySetdjango-queryset-sequence를 체크하는 것이 좋습니다.

하지만 그것에 대한 한 가지 메모.두 개면 된다querysets의론대로.하지만 비단뱀은reduce항상 여러 개에 적용할 수 있습니다.querysets.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

그리고 이것이 마지막입니다.아래는 내가 마주친 상황과 내가 어떻게 고용했는지이다.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})

아이디어가 하나 있는데...3개의 결과 각각에서 한 페이지씩 풀다운하고 가장 쓸모없는 20개의 결과만 버리면 됩니다.이를 통해 대규모 쿼리셋을 제거할 수 있으므로 많은 양의 쿼리셋이 아닌 약간의 성능만 희생할 수 있습니다.

가장 좋은 옵션은 Django 삽입 방식을 사용하는 것입니다.

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

그러면 해당 쿼리셋의 모든 객체의 결합이 반환됩니다.

3개의 쿼리셋에 포함된 객체만 가져오려면 내장된 쿼리셋 방식을 사용합니다.intersection.

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

다른 라이브러리를 사용하지 않고 작업을 수행합니다.

result_list = page_list | article_list | post_list

다음과 같이 "|"(비트 또는 )를 사용하여 동일한 모델의 쿼리 세트를 결합할 수 있습니다.

# "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

두 쿼리 세트의 교차점을 얻으려면:

result = first_queryset.intersection(second_queryset)

이 재귀 함수는 쿼리 세트의 배열을 하나의 쿼리 세트로 연결합니다.

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

언급URL : https://stackoverflow.com/questions/431628/how-to-combine-multiple-querysets-in-django

반응형