Django दृश्य में दो या अधिक क्वेरी को कैसे संयोजित करें?


653

मैं जिस Django साइट का निर्माण कर रहा हूं, उसकी खोज का निर्माण करने का प्रयास कर रहा हूं और उस खोज में, मैं 3 अलग-अलग मॉडलों में खोज रहा हूं। और खोज परिणाम सूची पर पृष्ठांकन प्राप्त करने के लिए, मैं परिणामों को प्रदर्शित करने के लिए एक सामान्य ऑब्जेक्ट_सूची दृश्य का उपयोग करना चाहूंगा। लेकिन ऐसा करने के लिए, मुझे 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_list, article_listऔर post_list?


T_rybik तरह लग रहा है पर एक व्यापक समाधान पैदा कर दी है djangosnippets.org/snippets/1933
akaihola

खोज के लिए हेस्टैक जैसे समर्पित समाधानों का उपयोग करना बेहतर है - यह बहुत लचीला है।
मनचला

1
Django उपयोगकर्ता 1.11 और abv, इस उत्तर को देखें - stackoverflow.com/a/42186970/6003362
साहिल अग्रवाल

नोट : यह प्रश्न बहुत ही दुर्लभ मामले तक सीमित है जब 3 अलग-अलग मॉडलों को एक साथ मर्ज करने के बाद आपको लिस्टिंग पर फिर से मॉडल निकालने की ज़रूरत नहीं है, ताकि वे डेटा पर अलग-अलग प्रकारों में अंतर कर सकें। अधिकांश मामलों के लिए - यदि भेद अपेक्षित है - यह गलत इंटरफ़ेस होगा। एक ही मॉडल के लिए: के बारे में जवाब देखें union
स्लावोमिर लेनर्ट

जवाबों:


1058

किसी सूची में क्वेरीसेट को सम्‍मिलित करना सबसे सरल तरीका है। यदि डेटाबेस को सभी क्वेरीज़ के लिए वैसे भी मारा जाएगा (जैसे कि परिणाम को क्रमबद्ध करने की आवश्यकता है), इससे आगे की लागत नहीं बढ़ेगी।

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.4 या उसके बाद का उपयोग कर रहे हैं, तो आप attrgetterलैम्बडा के बजाय उपयोग कर सकते हैं । मुझे याद है कि इसके बारे में पढ़ना तेजी से हो रहा है, लेकिन मैंने एक लाख आइटम सूची के लिए ध्यान देने योग्य गति अंतर नहीं देखा।

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

13
यदि OR या क्वेरी करने के लिए एक ही तालिका से क्वेरीज़ को मर्ज किया जाता है, और डुप्लिकेट की गई पंक्तियाँ हैं, तो आप उन्हें ग्रुपबी फ़ंक्शन से समाप्त कर सकते हैं: from itertools import groupby unique_results = [rows.next() for (key, rows) in groupby(result_list, key=lambda obj: obj.id)]
जोश रूसो

1
ठीक है, इसलिए इस संदर्भ में ग्रुपबी फ़ंक्शन के बारे में एनएम। क्यू समारोह के साथ आप या किसी को करने के लिए सक्षम होना चाहिए क्वेरी आप की जरूरत: https://docs.djangoproject.com/en/1.3/topics/db/queries/#complex-lookups-with-q-objects
जोश रूसो

2
@apelliciari चैन list.extend की तुलना में काफी कम मेमोरी का उपयोग करता है, क्योंकि इसमें दोनों सूचियों को पूरी तरह से मेमोरी में लोड करने की आवश्यकता नहीं होती है।
डेन गेल

2
@AWrightIV यहां उस लिंक का नया संस्करण है: docs.djangoproject.com/en/1.8/topics/db/queries/…
जोश रूसो

1
इस approacg की कोशिश कर रहा है, लेकिन है'list' object has no attribute 'complex_filter'
grillazz

466

इसे इस्तेमाल करे:

matches = pages | articles | posts

यह क्वेरी के सभी कार्यों को बरकरार रखता है जो यदि आप order_byया इसी तरह चाहते हैं तो अच्छा है ।

कृपया ध्यान दें: यह दो अलग-अलग मॉडलों के क्वेरीसेट पर काम नहीं करता है।


10
कटा हुआ क्वेरीसेट पर काम नहीं करता है, हालांकि। या क्या मैं कुछ न कुछ भूल रहा हूं?
sthzg

1
मैं "" | " लेकिन हमेशा ठीक काम नहीं करता है। "Q" का उपयोग करना बेहतर है: docs.djangoproject.com/en/dev/topics/db/queries/…
इग्नासियो पेरेज़

1
यह Django 1.6 का उपयोग कर, डुप्लिकेट बनाने के लिए प्रतीत नहीं होता है।
टेकािन

15
यहाँ |सेट यूनियन ऑपरेटर है, न कि बिटवाइज़ OR।
e100

6
@ e100 नहीं, यह सेट यूनियन ऑपरेटर नहीं है। django
बिटवाइज

109

संबंधित, एक ही मॉडल से या कुछ मॉडल से समान क्षेत्रों के लिए क्वेरीसेट मिश्रण करने के लिए, Django 1.11 के साथ शुरू एक qs.union()विधि यह भी है:

union()

union(*other_qs, all=False)

Django 1.11 में नया । दो या अधिक QuerySets के परिणामों को संयोजित करने के लिए SQL के UNION ऑपरेटर का उपयोग करता है। उदाहरण के लिए:

>>> qs1.union(qs2, qs3)

UNION ऑपरेटर डिफ़ॉल्ट रूप से केवल भिन्न मानों का चयन करता है। डुप्लिकेट मानों की अनुमति देने के लिए, सभी = सही तर्क का उपयोग करें।

संघ (), प्रतिच्छेदन (), और अंतर () रिटर्न मॉडल उदाहरणों के प्रकार के पहले QuerySet भले ही तर्क अन्य मॉडल के QuerySets हों। विभिन्न मॉडलों को पास करना तब तक काम करता है जब तक SELECT लिस्ट सभी QuerySets में समान हो (कम से कम प्रकार, नाम तब तक मायने नहीं रखते जब तक कि एक ही क्रम में प्रकार)।

इसके अलावा, परिणामी क्वेरी पर केवल LIMIT, OFFSET, और ORDER BY (यानी स्लाइसिंग और ऑर्डर_बी ()) की अनुमति है। इसके अलावा, डेटाबेस उन प्रश्नों पर प्रतिबंध लगाते हैं जिन्हें संयुक्त प्रश्नों में संचालन की अनुमति है। उदाहरण के लिए, अधिकांश डेटाबेस संयुक्त प्रश्नों में LIMIT या OFFSET की अनुमति नहीं देते हैं।

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


यह मेरी समस्या सेट के लिए एक बेहतर समाधान है जिसके लिए अद्वितीय मान होना चाहिए।
बर्निंग क्रिस्टल्स

जियोड्जैंगो जियोमेट्रीज के लिए काम नहीं करता है।
मरमट

यद्यपि आप संघ का आयात कहाँ से करते हैं? क्या यह किसी एक नंबर की क्वेरी से आना है?
जैक

हां, यह क्वेरीसेट का एक तरीका है।
उदी

मुझे लगता है कि यह खोज फ़िल्टर
पियरे कॉर्डियर

76

आप 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मॉड्यूल अजगर 2.3 में पेश किया गया था, तो यह सब अजगर संस्करणों Django पर चलता में उपलब्ध होना चाहिए।


5
अच्छा तरीका है, लेकिन एक समस्या जो मुझे यहाँ दिख रही है वह यह है कि क्वेरी सेट को "हेड-टू-टेल" जोड़ा गया है। क्या होगा यदि प्रत्येक क्वेरी तिथि के अनुसार आदेशित की जाती है और किसी को संयुक्त-सेट की भी तिथि के अनुसार ऑर्डर करने की आवश्यकता होती है?
hasen

यह निश्चित रूप से आशाजनक लग रहा है, महान, मुझे कोशिश करनी होगी कि, लेकिन मेरे पास आज समय नहीं है। अगर यह मेरी समस्या को हल कर देता है, तो मैं आपके पास वापस आऊंगा। अच्छा कार्य।
espenhogbakk

ठीक है, मुझे आज कोशिश करनी थी, लेकिन यह काम नहीं किया, पहले यह शिकायत की कि यह _clone विशेषता के लिए नहीं था, इसलिए मैंने कहा कि एक, बस _all की नकल की और वह काम किया, लेकिन ऐसा लगता है कि पेजिनेटर को इस क्वेरी के साथ कुछ समस्या है। मुझे यह पेजिनेटर त्रुटि मिलती है: "बिना ऑब्जेक्ट के लेन ()"
espenhogbakk

1
@ एसेन पायथन लाइब्रेरी: पीडीबी, लॉगिंग। बाहरी: IPython, ipdb, django-logging, django-debug-toolbar, django-कमांड-एक्सटेंशन, werkzeug। कोड में प्रिंट स्टेटमेंट का उपयोग करें या लॉगिंग मॉड्यूल का उपयोग करें। इन सबसे ऊपर, शेल में आत्मनिरीक्षण करना सीखें। Google ब्लॉग के लिए Django डिबगिंग के बारे में पोस्ट करता है। मदद करने में खुशी!
एकैहोला

4
@patrick देखें djangosnippets.org/snippets/1103 और djangosnippets.org/snippets/1933 - समय -समय पर उत्तरार्द्ध एक बहुत व्यापक समाधान है
akaihola

27

आपके वर्तमान दृष्टिकोण का बड़ा पहलू बड़े खोज परिणाम सेटों के साथ इसकी अक्षमता है, क्योंकि आपको हर बार डेटाबेस से सेट किए गए संपूर्ण परिणाम को नीचे खींचना पड़ता है, भले ही आप केवल परिणामों का एक पृष्ठ प्रदर्शित करने का इरादा रखते हों।

केवल उन वस्तुओं को खींचने के लिए जिनकी आपको वास्तव में डेटाबेस से ज़रूरत है, आपको एक क्वेरी पर एक पृष्ठ पर नहीं, क्वेरीज़ पर उपयोग करना होगा। यदि आप ऐसा करते हैं, तो क्वेरी के निष्पादित होने से पहले, Django वास्तव में QuerySet को स्लाइस करता है, इसलिए SQL क्वेरी OFFSET और LIMIT का उपयोग केवल उन रिकॉर्ड्स को प्राप्त करने के लिए करेगी जिन्हें आप वास्तव में प्रदर्शित करेंगे। लेकिन आप ऐसा नहीं कर सकते जब तक कि आप अपनी खोज को किसी भी तरह से एक क्वेरी में रटना नहीं कर सकते।

यह देखते हुए कि आपके सभी तीन मॉडलों में शीर्षक और शरीर फ़ील्ड हैं, तो मॉडल वंशानुक्रम का उपयोग क्यों नहीं किया जाता है ? बस सभी तीन मॉडल एक सामान्य पूर्वज से विरासत में मिले हैं जिसका शीर्षक और शरीर है, और पूर्वज मॉडल पर एकल क्वेरी के रूप में खोज करते हैं।


23

यदि आप बहुत सारे क्वेरीज़ को चेन करना चाहते हैं, तो यह कोशिश करें:

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

कहाँ: डॉक्स क्वेरीसेट की एक सूची है


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)

Https://groups.google.com/forum/# .topic / django-users / 6wUNuJa4VVw से उद्धृत । एलेक्स गेन्नोर देखें


8

यह दो तरीकों से प्राप्त किया जा सकता है।

ऐसा करने का 1 तरीका

|दो क्वेरी के मिलन के लिए क्वेरी के लिए यूनियन ऑपरेटर का उपयोग करें । यदि दोनों क्वेरी एक ही मॉडल / एकल मॉडल से संबंधित है, तो यूनियन ऑपरेटर का उपयोग करके क्वेरी को संयोजित करना संभव है।

एक उदाहरण के लिए

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

7

आवश्यकताएँ: Django==2.0.2 ,django-querysetsequence==0.8

यदि आप संयोजन करना चाहते हैं querysetsऔर अभी भी बाहर आना QuerySetचाहते हैं, तो आप django-queryset-अनुक्रम की जांच कर सकते हैं ।

लेकिन एक नोट इसके बारे में। यह केवल दो लेता है querysetsक्योंकि यह तर्क है। लेकिन अजगर के साथ reduceआप इसे कई querysetएस पर लागू कर सकते हैं ।

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

1
Book.objects.filter(owner__mentor=mentor)एक ही काम नहीं करता है? मुझे यकीन नहीं है कि यह एक वैध उपयोग-मामला है। मुझे लगता है कि इस तरह से कुछ भी शुरू करने से पहले आपको Bookकई ownerएस की आवश्यकता हो सकती है ।
विल एस

हाँ, यह वही काम करता है। मैं इसे करने की कोशिश की। वैसे भी, शायद यह किसी अन्य स्थिति में उपयोगी हो सकता है। यह बात बताने के लिए धन्यवाद। आप बिल्कुल शुरुआत के रूप में सभी शॉर्टकट को जानने से शुरू नहीं करते हैं। कभी-कभी आप कौवा के उड़ने की सराहना करने के लिए लोड घुमावदार सड़क की यात्रा करते हैं
चिड़िमो

6

यहाँ एक विचार है ... बस तीन में से प्रत्येक से परिणामों का एक पूरा पृष्ठ नीचे खींचें और फिर 20 सबसे कम उपयोगी लोगों को बाहर फेंक दें ... यह बड़े क्वेरीसेट को समाप्त कर देता है और इस तरह आप केवल बहुत सारे के बजाय थोड़ा सा प्रदर्शन करते हैं



-1

यह पुनरावर्ती फ़ंक्शन क्वेरी के सरणी को एक क्वेरीसेट में समेट देता है।

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

1
मैं सचमुच खो गया हूँ।
लाइकोइड

हम क्वेरी परिणाम के संयोजन का उपयोग रन-टाइम पर नहीं कर सकते हैं और ऐसा करने के लिए वास्तव में बुरा विचार है। क्योंकि कभी-कभी यह परिणाम पर दोहराव जोड़ देता है।
देवांग हिंगु
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.