Django केवल डुप्लिकेट फ़ील्ड मानों वाली पंक्तियों का चयन करता है


96

मान लें कि हमारे पास django में एक मॉडल इस प्रकार है:

class Literal:
    name = models.CharField(...)
    ...

नाम फ़ील्ड अद्वितीय नहीं है, और इस प्रकार डुप्लिकेट मान हो सकते हैं। मुझे निम्नलिखित कार्य को पूरा करने की आवश्यकता है: उस मॉडल से सभी पंक्तियों का चयन करें जिसमें फ़ील्ड का कम से कम एक डुप्लिकेट मान है name

मुझे पता है कि इसे सादे एसक्यूएल का उपयोग कैसे करना है (यह सबसे अच्छा समाधान नहीं हो सकता है):

select * from literal where name IN (
    select name from literal group by name having count((name)) > 1
);

तो, क्या django ORM का उपयोग करके इसे चुनना संभव है? या बेहतर एसक्यूएल समाधान?

जवाबों:


193

प्रयत्न:

from django.db.models import Count
Literal.objects.values('name')
               .annotate(Count('id')) 
               .order_by()
               .filter(id__count__gt=1)

यह उतना ही करीब है जितना आप Django के साथ मिल सकते हैं। समस्या यह है कि यह ValuesQuerySetकेवल nameऔर के साथ वापस आ जाएगा count। हालाँकि, आप इसका उपयोग किसी QuerySetअन्य क्वेरी में वापस फीड करके एक नियमित निर्माण के लिए कर सकते हैं :

dupes = Literal.objects.values('name')
                       .annotate(Count('id'))
                       .order_by()
                       .filter(id__count__gt=1)
Literal.objects.filter(name__in=[item['name'] for item in dupes])

4
शायद आपका मतलब है Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)?
ड्रैगून

मूल क्वेरी देता हैCannot resolve keyword 'id_count' into field
ड्रैगून

2
अपडेट किए गए उत्तर के लिए धन्यवाद, मुझे लगता है कि मैं इस समाधान के साथ रहूंगा, आप इसका उपयोग बिना सूची समझ के भी कर सकते हैंvalues_list('name', flat=True)
ड्रैगून

1
Django पहले इस पर एक बग था (हाल के संस्करणों में तय किया गया हो सकता है) यदि आप Countएनोटेशन के रूप में सहेजने के लिए एक फ़ील्डनाम निर्दिष्ट नहीं करते हैं , तो यह चूक करता है [field]__count। हालाँकि, वह डबल-अंडरस्कोर सिंटैक्स यह भी है कि कैसे Django आपको सम्मिलित होने की इच्छा करता है। इसलिए, अनिवार्य रूप से जब आप उस पर फ़िल्टर करने का प्रयास करते हैं, तो Django सोचता है कि आप एक ऐसा कार्य करने की कोशिश कर रहे हैं countजिसके साथ स्पष्ट रूप से मौजूद नहीं है। फिक्स आपके एनोटेशन परिणाम के लिए एक नाम निर्दिष्ट करने के लिए है, अर्थात annotate(mycount=Count('id'))और फिर mycountइसके बजाय फ़िल्टर करें ।
क्रिस प्रैट

1
यदि आप values('name')अपने कॉल के एनोटेट के बाद एक और कॉल जोड़ते हैं , तो आप सूची की समझ को हटा सकते हैं और कह Literal.objects.filter(name__in=dupes)सकते हैं कि यह सभी को एक ही क्वेरी में निष्पादित करने की अनुमति देगा।
पाइपर मरियम

43

इसे एक संपादन के रूप में अस्वीकार कर दिया गया था। इसलिए यहां यह एक बेहतर जवाब है

dups = (
    Literal.objects.values('name')
    .annotate(count=Count('id'))
    .values('name')
    .order_by()
    .filter(count__gt=1)
)

यह ValuesQuerySetसभी डुप्लिकेट नामों के साथ वापस आ जाएगा । हालाँकि, आप इसका उपयोग किसी QuerySetअन्य क्वेरी में वापस फीड करके एक नियमित निर्माण के लिए कर सकते हैं । Django ORM को एक ही क्वेरी में संयोजित करने के लिए पर्याप्त स्मार्ट है:

Literal.objects.filter(name__in=dups)

.values('name')एनोटेट कॉल के बाद अतिरिक्त कॉल थोड़ा अजीब लगता है। इसके बिना, उपश्रेणी विफल हो जाती है। अतिरिक्त मान ORM को केवल उप-वर्ग के लिए नाम स्तंभ का चयन करने के लिए ट्रिक करता है।


अच्छी चाल, दुर्भाग्य से यह केवल तभी काम करेगा जब केवल एक मूल्य का उपयोग किया जाता है (जैसे। यदि 'नाम' और 'फोन' दोनों का उपयोग किया जाता है, तो अंतिम भाग काम नहीं करेगा)।
गुरूवार

1
किसलिए है .order_by()?
स्टेफानफोलिस

4
@stefanfoulis यह किसी भी मौजूदा आदेश को साफ करता है। यदि आपके पास एक मॉडल-सेट ऑर्डर है, तो यह एसक्यूएल GROUP BYक्लॉज का हिस्सा बन जाता है , और यह चीजों को तोड़ देता है। पता चला है कि जब सबक्वेरी के साथ खेल रहे हैं (जिसमें आप बहुत समान ग्रुपिंग करते हैं .values())
ओली

10

एकत्रीकरण का उपयोग करके देखें

Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)

ठीक है, जो नामों की गलियारे की सूची देता है, लेकिन क्या एक ही समय में आईडी और अन्य क्षेत्रों का चयन करना संभव है?
ड्रैगून

@ डैरागून - नहीं, लेकिन क्रिस प्रैट ने अपने जवाब में विकल्प को कवर किया है।
जेम्सओ

5

यदि आप PostgreSQL का उपयोग करते हैं, तो आप कुछ इस तरह से कर सकते हैं:

from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Func, Value

duplicate_ids = (Literal.objects.values('name')
                 .annotate(ids=ArrayAgg('id'))
                 .annotate(c=Func('ids', Value(1), function='array_length'))
                 .filter(c__gt=1)
                 .annotate(ids=Func('ids', function='unnest'))
                 .values_list('ids', flat=True))

इसका परिणाम सरल SQL क्वेरी है:

SELECT unnest(ARRAY_AGG("app_literal"."id")) AS "ids"
FROM "app_literal"
GROUP BY "app_literal"."name"
HAVING array_length(ARRAY_AGG("app_literal"."id"), 1) > 1

0

यदि आप केवल नाम सूची लेकिन वस्तुओं का परिणाम नहीं चाहते हैं, तो आप निम्नलिखित क्वेरी का उपयोग कर सकते हैं

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.