2015-06-10 5 views
47

सरल Django मॉडल Event और Participant पर विचार करें:Django में गिनती एनोटेशन के लिए ऑब्जेक्ट्स को फ़िल्टर कैसे करें?

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 की आवश्यकता है।

example from documentation यहां काम नहीं करता है, क्योंकि इसमें 0 के साथ टिप्पणी करने के बजाय क्वेरी से वस्तुओं को शामिल नहीं किया गया है।

अद्यतन। Django 1.8 नई conditional expressions feature है, तो अब हम इस तरह कर सकते हैं:

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 नई Conditional aggregation सुविधा है, नीचे दिए गए the accepted answer देखते हैं।

उत्तर

6

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

events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True)) 
) 

वहाँ filtering on annotations के बारे में किए गए दस्तावेज़ों में एक अलग अनुभाग है:

वैसे भी, आपके मामले में, हम के रूप में सरल रूप में कुछ देख रहे हैं। यह सशर्त एकत्रीकरण के समान सामान है लेकिन ऊपर मेरे उदाहरण की तरह। किसी भी तरह से, यह पहले से कर रहे gnarly subqueries से बहुत स्वस्थ है।

+0

यह शानदार लग रहा है! :) – rudyryk

+0

बीटीडब्ल्यू, दस्तावेज लिंक द्वारा ऐसा कोई उदाहरण नहीं है, केवल 'कुल' उपयोग दिखाया गया है। क्या आपने पहले से ही ऐसे प्रश्नों का परीक्षण किया है? (मैंने नहीं किया है और मैं विश्वास करना चाहता हूं! :) – rudyryk

+2

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

24

अद्यतन

उप क्वेरी दृष्टिकोण है जो मैं उल्लेख अब subquery-expressions के माध्यम से 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`" 
}) 
+0

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

+1

आपका स्वागत है, बीटीडब्ल्यू मुझे इस दृष्टिकोण से अवगत है, लेकिन अब तक यह एक गैर-कामकाजी समाधान था, इसलिए मैंने इसके बारे में उल्लेख नहीं किया। हालांकि मुझे अभी पता चला है कि इसे 'Django 1.8.2' में तय किया गया है, इसलिए मुझे लगता है कि आप उस संस्करण के साथ हैं और यही कारण है कि यह आपके लिए काम कर रहा है। आप इसके बारे में और अधिक पढ़ सकते हैं [यहां] (http: // stackoverflow।कॉम/प्रश्न/2 9 440374/डीजेंगो-एनोटेट-एंड-गिन-कैसे-टू-फ़िल्टर-द-टू-इन-इन-गिनती) और [यहां] (https://code.djangoproject.com/ticket/24766) – Todor

+0

मुझे लगता है कि यह कोई नहीं बनता है जब यह 0 होना चाहिए। किसी और को यह मिल रहा है? – Splatmistro

70

बस पता चला कि Django 1.8 है नई conditional expressions feature का उपयोग कर प्राप्त किया जा सकता है, इसलिए अब हम इस तरह कर सकते हैं:

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() 
    ))) 
+0

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

+0

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

+0

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

1

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

कम के लिए, आप क्या करना चाहते हैं क्या द्वारा दिया जाता है:

  1. बनाएं 2 Event रों:

    event1 = Event.objects.create(title='event1') 
    event2 = Event.objects.create(title='event2') 
    
  2. Participant.objects\ 
        .filter(is_paid=True)\ 
        .values('event')\ 
        .distinct()\ 
        .annotate(models.Count('id')) 
    

    एक पूरा उदाहरण के रूप में अनुवर्ती है 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. समूह उनके event क्षेत्र द्वारा सभी Participant रों:

    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. अंत में आप एक is_paid जा रहा है True के साथ ही चाहते Participant , तो आप सिर्फ पिछले एक्सप्रेशन के सामने एक फिल्टर जोड़ सकते हैं, और इस उपज अभिव्यक्ति ऊपर दिखाए:

    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 है।

संबंधित मुद्दे