कैसे Django में गणना एनोटेशन के लिए वस्तुओं को फ़िल्टर करने के लिए?


123

सरल Django मॉडल पर विचार करें Eventऔर Participant:

class Event(models.Model):
    title = models.CharField(max_length=100)

class Participant(models.Model):
    event = models.ForeignKey(Event, db_index=True)
    is_paid = models.BooleanField(default=False, db_index=True)

कुल प्रतिभागियों के साथ ईवेंट क्वेरी को एनोटेट करना आसान है:

events = Event.objects.all().annotate(participants=models.Count('participant'))

फ़िल्टर किए गए प्रतिभागियों की गिनती के साथ एनोटेट कैसे करें is_paid=True?

मुझे प्रतिभागियों की संख्या की परवाह किए बिना सभी घटनाओं को क्वेरी करने की आवश्यकता है , उदाहरण के लिए मुझे एनोटेट परिणाम द्वारा फ़िल्टर करने की आवश्यकता नहीं है। यदि 0प्रतिभागी हैं, तो यह ठीक है, मुझे सिर्फ 0एनोटेट वैल्यू की आवश्यकता है।

दस्तावेज़ से उदाहरण यहाँ काम नहीं करता है, क्योंकि यह शामिल नहीं उन लोगों के साथ व्याख्या के बजाय खोज क्वेरी से वस्तुओं 0

अपडेट करें। Django 1.8 में नई सशर्त अभिव्यक्ति की सुविधा है , इसलिए अब हम इस तरह कर सकते हैं:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0,
        output_field=models.IntegerField()
    )))

अद्यतन 2. Django 2.0 में नई सशर्त एकत्रीकरण सुविधा है, नीचे दिए गए उत्तर को देखें।

जवाबों:


105

Django 2.0 में सशर्त एकत्रीकरण आपको अतीत में हुए फाफ की मात्रा को और कम करने की अनुमति देता है। यह पोस्टग्रैज filterलॉजिक का भी उपयोग करेगा , जो सम-केस की तुलना में कुछ अधिक तेज है (मैंने 20-30% के आसपास संख्याओं को देखा है)।

वैसे भी, आपके मामले में, हम कुछ सरल के रूप में देख रहे हैं:

from django.db.models import Q, Count
events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

एनोटेशन पर फ़िल्टर करने के बारे में डॉक्स में एक अलग सेक्शन है । यह सशर्त एकत्रीकरण के समान सामान है लेकिन ऊपर मेरे उदाहरण की तरह अधिक है। किसी भी तरह से, यह मेरे द्वारा पहले की गई गैनरी उपश्रेणियों की तुलना में बहुत स्वस्थ है।


BTW, प्रलेखन लिंक द्वारा ऐसा कोई उदाहरण नहीं है, केवल aggregateउपयोग दिखाया गया है। क्या आपने पहले से ही इस तरह के प्रश्नों का परीक्षण किया है? (मैं नहीं है और मैं विश्वास करना चाहता हूँ! :)
rudyryk

2
मेरे पास है। वे काम करते हैं। मैंने वास्तव में एक अजीब पैच मारा, जहां एक पुराने (सुपर-कॉम्प्लेक्स) सबक्वेरी ने Django 2.0 में अपग्रेड करने के बाद काम करना बंद कर दिया और मैं इसे सुपर-सिंपल फ़िल्टर-काउंट के साथ बदलने में कामयाब रहा। एनोटेशन के लिए एक बेहतर डॉक उदाहरण है, इसलिए अब में इसे खींचूंगा।
ओली

1
यहाँ कुछ जवाब दिए गए हैं, यह Django 2.0 तरीका है, और नीचे आपको Django 1.11 (सबक्वेरीज़) का रास्ता मिलेगा, और Django 1.8 रास्ता।
रयान कास्टनर

2
खबरदार, अगर आप इस Django <2 में कोशिश, 1.9 जैसे, यह होगा बिना किसी अपवाद के चलते हैं, लेकिन फिल्टर बस लागू नहीं होता। तो यह Django <2 के साथ काम करने के लिए प्रकट हो सकता है, लेकिन नहीं।
djvg

यदि आपको कई फ़िल्टर जोड़ने की आवश्यकता है, तो आप उन्हें क्यू () तर्क से अलग करके जोड़ सकते हैं, उदाहरण के लिए फ़िल्टर = क्यू (प्रतिभागी_आईएस_पेड = सच,
someelse

93

बस पता चला है कि Django 1.8 में नई सशर्त अभिव्यक्ति की सुविधा है , इसलिए अब हम इस तरह कर सकते हैं:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0, output_field=models.IntegerField()
    )))

क्या यह एक योग्य समाधान है जब मिलान आइटम कई हैं? हम कहते हैं कि मैं उन घटनाओं को गिनना चाहता हूं जो नवीनतम सप्ताह में हुई थीं।
SverkerSbrg

क्यों नहीं? मेरा मतलब है, आपका मामला अलग क्यों है? ऊपर के मामले में घटना में किसी भी संख्या में भुगतान किए गए प्रतिभागियों द्वारा किया जा सकता है।
rudyryk

मुझे लगता है कि सवाल @SverkerSbrg पूछ रहा है कि क्या यह बड़े सेटों के लिए अक्षम है, बजाय इसके कि यह काम करेगा या नहीं .... सही है? सबसे महत्वपूर्ण बात यह है कि यह अजगर में नहीं कर रहा है, यह एक SQL केस क्लॉज बना रहा है - देखें github.com/django/django/blob/master/django/db/models/… - तो यह यथोचित प्रदर्शन करने वाला होगा, सरल उदाहरण एक जुड़ने से बेहतर होगा, लेकिन अधिक जटिल संस्करणों में उपश्रेणियां शामिल हो सकती हैं
हेडन क्रोकर

1
Countइसके साथ इसका उपयोग करते समय (इसके बजाय Sum) मुझे लगता है कि हमें सेट करना चाहिए default=None(यदि django 2 filterतर्क का उपयोग नहीं कर रहा है)।
djvg

41

अपडेट करें

उप-क्वेरी दृष्टिकोण जो मैं उल्लेख करता हूं, अब उप -अभिव्यक्ति के माध्यम से Django 1.11 में समर्थित है ।

Event.objects.annotate(
    num_paid_participants=Subquery(
        Participant.objects.filter(
            is_paid=True,
            event=OuterRef('pk')
        ).values('event')
        .annotate(cnt=Count('pk'))
        .values('cnt'),
        output_field=models.IntegerField()
    )
)

मैं इसे एकत्रीकरण (राशि + मामले) से अधिक पसंद करता हूं , क्योंकि इसे (उचित अनुक्रमण के साथ) अनुकूलित किया जाना तेज और आसान होना चाहिए ।

पुराने संस्करण के लिए, उसी का उपयोग करके प्राप्त किया जा सकता है .extra

Event.objects.extra(select={'num_paid_participants': "\
    SELECT COUNT(*) \
    FROM `myapp_participant` \
    WHERE `myapp_participant`.`is_paid` = 1 AND \
            `myapp_participant`.`event_id` = `myapp_event`.`id`"
})

धन्यवाद टोडर! लगता है जैसे मैंने बिना उपयोग के रास्ता पा लिया है .extra, जैसा कि मैं Django में SQL से बचना पसंद करता हूं :) मैं सवाल को अपडेट करूंगा।
rudyryk

1
आपका स्वागत है, btw मैं इस दृष्टिकोण से अवगत हूँ, लेकिन यह अब तक एक गैर-कार्यशील समाधान था, इसीलिए मैंने इसके बारे में उल्लेख नहीं किया। हालाँकि, मैंने अभी पाया कि इसे ठीक कर दिया गया है Django 1.8.2, इसलिए मुझे लगता है कि आप उस संस्करण के साथ हैं और इसीलिए यह आपके लिए काम कर रहा है। आप इस बारे में और अधिक पढ़ सकते यहाँ और यहाँ
Todor

2
मुझे लगता है कि यह एक कोई भी पैदा करता है जब यह होना चाहिए 0. किसी और को यह हो रहा है?
स्टीफनजॉलियर

@StefanJCollier हां, मुझे Noneभी मिला । मेरा समाधान का उपयोग करना था Coalesce( from django.db.models.functions import Coalesce)। आप इसे इस तरह का उपयोग करें: Coalesce(Subquery(...), 0)। हालांकि एक बेहतर तरीका हो सकता है।
एडम टेलर

6

मैं इसके बजाय .valuesआपके Participantक्वेरीसेट की विधि का उपयोग करने का सुझाव दूंगा।

संक्षेप में, आप क्या करना चाहते हैं:

Participant.objects\
    .filter(is_paid=True)\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))

एक पूर्ण उदाहरण निम्नानुसार है:

  1. 2 Eventएस बनाएँ :

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
  2. Participantउन्हें जोड़ें :

    part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))\
              for _ in range(10)]
    part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))\
              for _ in range(50)]
  3. Participantअपने eventक्षेत्र द्वारा सभी समूह :

    Participant.objects.values('event')
    > <QuerySet [{'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, '...(remaining elements truncated)...']>

    यहाँ अलग की जरूरत है:

    Participant.objects.values('event').distinct()
    > <QuerySet [{'event': 1}, {'event': 2}]>

    यहाँ क्या कर रहे हैं .valuesऔर .distinctयह है कि वे Participantअपने तत्व से समूहीकृत दो बाल्टी बना रहे हैं event। ध्यान दें कि उन बाल्टियों में हैं Participant

  4. फिर आप उन बाल्टियों को एनोटेट कर सकते हैं क्योंकि उनमें मूल का सेट होता है Participant। यहाँ हम की संख्या की गणना करना चाहते Participant, यह बस की गणना के द्वारा किया जाता है idउन बाल्टियों में तत्वों की है (उन के बाद से कर रहे हैं Participant):

    Participant.objects\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
  5. अंत में आप केवल Participantएक is_paidहोने के साथ चाहते हैं True, आप पिछले अभिव्यक्ति के सामने सिर्फ एक फिल्टर जोड़ सकते हैं, और यह ऊपर दिखाए गए अभिव्यक्ति का उत्पादन करता है:

    Participant.objects\
        .filter(is_paid=True)\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 5}, {'event': 2, 'id__count': 25}]>

एकमात्र दोष यह है कि आपको Eventबाद में पुनः प्राप्त करना होगा क्योंकि आपके पास केवल idऊपर की विधि है।


2

मुझे किस परिणाम की तलाश है:

  • वे लोग (असाइनमेंट) जिनके पास रिपोर्ट में जोड़े गए कार्य हैं। - लोगों की कुल अनूठी गिनती
  • ऐसे लोग जिनके पास कार्य हैं, एक रिपोर्ट में जोड़ दिए गए हैं, लेकिन कार्य के लिए जिनकी बिलबिलिटी केवल 0 से अधिक है।

सामान्य तौर पर, मुझे दो अलग-अलग प्रश्नों का उपयोग करना होगा:

Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()

लेकिन मैं दोनों को एक क्वेरी में चाहता हूं। अत:

Task.objects.values('report__title').annotate(withMoreThanZero=Count('assignee', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count('assignee', distinct=True))

परिणाम:

<QuerySet [{'report__title': 'TestReport', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}, {'report__title': 'Utilization_Report_April_2019', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}]>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.