วิธีการสืบค้นเป็น GROUP BY ใน django?


332

ฉันสอบถามรุ่น:

Members.objects.all()

และมันจะส่งคืน:

Eric, Salesman, X-Shop
Freddie, Manager, X2-Shop
Teddy, Salesman, X2-Shop
Sean, Manager, X2-Shop

สิ่งที่ฉันต้องการคือการรู้วิธีที่ดีที่สุดในการยิงgroup_byแบบสอบถามไปยังฐานข้อมูลของฉันเช่น:

Members.objects.all().group_by('designation')

ซึ่งใช้งานไม่ได้แน่นอน ฉันรู้ว่าเราสามารถทำเทคนิคบางอย่างdjango/db/models/query.pyได้ แต่ฉันแค่อยากรู้ว่าจะทำยังไงโดยไม่ต้องทำการปะ

คำตอบ:


483

หากคุณต้องการทำการรวมคุณสามารถใช้คุณลักษณะการรวมของ ORM :

from django.db.models import Count
Members.objects.values('designation').annotate(dcount=Count('designation'))

ผลลัพธ์นี้ในแบบสอบถามที่คล้ายกับ

SELECT designation, COUNT(designation) AS dcount
FROM members GROUP BY designation

และผลลัพธ์จะเป็นของแบบฟอร์ม

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]

6
@Harry: คุณสามารถโยงมันได้ สิ่งที่ต้องการ:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
อีไล

57
ฉันมีคำถามแบบสอบถามนี้กลับเฉพาะการกำหนดและ dcount ถ้าฉันต้องการรับค่าอื่น ๆ ของตารางด้วยหรือไม่
AJ

19
โปรดทราบว่าหากการเรียงลำดับของคุณเป็นฟิลด์อื่นนอกเหนือจากการกำหนดจะไม่ทำงานหากไม่มีการรีเซ็ตการเรียงลำดับ ดูstackoverflow.com/a/1341667/202137
Gidgidonihah

12
@Gidgidonihah True ตัวอย่างควรอ่านMembers.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix

7
ฉันมีคำถามแบบสอบถามนี้กลับเฉพาะการกำหนดและ dcount ถ้าฉันต้องการรับค่าอื่น ๆ ของตารางด้วยหรือไม่
Yann 叶

55

ทางออกที่ง่าย แต่ไม่ใช่วิธีที่เหมาะสมคือใช้raw SQL :

results = Members.objects.raw('SELECT * FROM myapp_members GROUP BY designation')

วิธีแก้ไขอื่นคือใช้group_byคุณสมบัติ:

query = Members.objects.all().query
query.group_by = ['designation']
results = QuerySet(query=query, model=Members)

ตอนนี้คุณสามารถวนซ้ำตัวแปรผลลัพธ์เพื่อดึงผลลัพธ์ของคุณ โปรดทราบว่าgroup_byไม่มีการบันทึกไว้และอาจมีการเปลี่ยนแปลงในรุ่นต่อไปของ Django

และ ... คุณต้องการใช้group_byทำไม หากคุณไม่ได้ใช้การรวมคุณสามารถใช้order_byเพื่อให้ได้ผลลัพธ์ที่เหมือนกัน


คุณช่วยบอกวิธีการใช้ order_by ได้ไหม
simplyharsh

2
สวัสดีถ้าคุณไม่ได้ใช้การรวมคุณสามารถเลียนแบบ group_by โดยใช้ order_by และกำจัดรายการที่คุณไม่ต้องการ แน่นอนว่านี่เป็นการจำลองและใช้ได้เฉพาะเมื่อใช้ข้อมูลไม่มากเท่านั้น เนื่องจากเขาไม่ได้พูดถึงการรวมฉันคิดว่ามันอาจเป็นทางออก
Michael

เฮ้นี่มันเยี่ยมมาก - คุณช่วยอธิบายวิธีใช้ execute_sql ได้ไหมว่ามันใช้งานไม่ได้ ..
rh0dium

8
โปรดทราบว่านี่ใช้งานไม่ได้กับ Django 1.9 อีกต่อไป stackoverflow.com/questions/35558120/…
grokpot

1
นี่เป็นวิธีแฮ็ค - อิชที่ใช้ ORM คุณไม่ควรสร้างอินสแตนซ์คิวรีใหม่ที่ส่งผ่านชุดข้อมูลเก่าด้วยตนเอง
เอียนเคิร์กแพททริก

32

คุณยังสามารถใช้regroupเทมเพลตแท็กเพื่อจัดกลุ่มตามคุณสมบัติ จากเอกสาร:

cities = [
    {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'},
    {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'},
    {'name': 'New York', 'population': '20,000,000', 'country': 'USA'},
    {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'},
    {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'},
]

...

{% regroup cities by country as country_list %}

<ul>
    {% for country in country_list %}
        <li>{{ country.grouper }}
            <ul>
            {% for city in country.list %}
                <li>{{ city.name }}: {{ city.population }}</li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

ดูเหมือนว่านี้:

  • อินเดีย
    • มุมไบ: 19,000,000
    • กัลกัตตา: 15,000,000
  • สหรัฐอเมริกา
    • นิวยอร์ก: 20,000,000
    • ชิคาโก: 7,000,000
  • ประเทศญี่ปุ่น
    • โตเกียว: 33,000,000

มันใช้ได้กับQuerySets ที่ฉันเชื่อด้วย

แหล่งที่มา: https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#regroup

แก้ไข: โปรดทราบว่าregroupแท็กใช้งานไม่ได้ตามที่คุณคาดหวังหากรายการพจนานุกรมของคุณไม่มีการเรียงลำดับคีย์ มันทำงานซ้ำแล้วซ้ำอีก ดังนั้นเรียงลำดับรายการของคุณ (หรือชุดแบบสอบถาม) โดยใช้คีย์ของปลากะรังก่อนส่งผ่านไปยังregroupแท็ก


1
มันสมบูรณ์แบบ! ฉันได้ค้นหาวิธีการง่ายๆในการทำสิ่งนี้มาก และมันก็ใช้ได้กับชุดการสืบค้นด้วยเช่นกันนั่นคือวิธีที่ฉันใช้
CarmenA

1
นี่เป็นความผิดทั้งหมดถ้าคุณอ่านจากฐานข้อมูลชุดใหญ่แล้วใช้ค่ารวม
Sławomir Lenart

@ SławomirLenartแน่นอนว่าสิ่งนี้อาจไม่ได้มีประสิทธิภาพเท่ากับการสืบค้น DB แบบตรง แต่สำหรับกรณีการใช้งานที่เรียบง่ายมันอาจเป็นคำตอบที่ดี
inostia

สิ่งนี้จะใช้งานได้หากผลลัพธ์ที่ปรากฏในเทมเพลต แต่สำหรับ JsonResponse หรือการตอบสนองทางอ้อมอื่น ๆ วิธีนี้จะไม่ทำงาน
Willy satrio nugroho

1
@Willysatrionugroho หากคุณต้องการทำในมุมมองเช่นstackoverflow.com/questions/477820/..อาจทำงานให้คุณ
inostia

7

คุณต้องทำ SQL แบบกำหนดเองดังที่แสดงไว้ในตัวอย่างนี้:

SQL ที่กำหนดเองผ่านแบบสอบถามย่อย

หรือในผู้จัดการที่กำหนดเองตามที่แสดงในเอกสาร Django ออนไลน์:

การเพิ่มวิธีการจัดการพิเศษ


1
ชนิดของโซลูชันไปกลับ ฉันจะใช้มันถ้าฉันมีการใช้อย่างกว้างขวาง แต่ที่นี่ฉันแค่ต้องการจำนวนสมาชิกต่อการแต่งตั้งนั่นคือทั้งหมด
Simplyharsh

ไม่มีปัญหา. ฉันคิดเกี่ยวกับการกล่าวขวัญ 1.1 คุณลักษณะรวม แต่ทำสมมติฐานที่คุณกำลังใช้รุ่นที่วางจำหน่าย :)
Van Gale

ทุกอย่างเกี่ยวกับการใช้ข้อความค้นหาแบบดิบซึ่งแสดงจุดอ่อนของ ORM ของ Django
Sławomir Lenart

5

Django ไม่สนับสนุนกลุ่มฟรีโดยคำสั่ง ฉันเรียนรู้มันในทางที่ไม่ดีมาก ORM ไม่ได้ออกแบบมาเพื่อรองรับสิ่งที่คุณต้องการโดยไม่ต้องใช้ SQL แบบกำหนดเอง คุณถูก จำกัด ที่:

  • RAW sql (เช่น MyModel.objects.raw ())
  • cr.execute ประโยค (และการแยกวิเคราะห์ผลที่ทำด้วยมือ)
  • .annotate() (กลุ่มโดยประโยคจะดำเนินการในโมเดลย่อยสำหรับ. annotate () ในตัวอย่างเช่นการรวม lines_count = Count ('lines'))

qsคุณสามารถโทรหาชุดข้อมูลได้qs.query.group_by = ['field1', 'field2', ...]แต่จะมีความเสี่ยงหากคุณไม่รู้ว่าคุณกำลังแก้ไขแบบสอบถามใดและไม่รับประกันว่าจะทำงานได้และไม่ทำลายภายในของวัตถุ QuerySet นอกจากนี้ยังเป็น API ภายใน (ที่ไม่มีเอกสารประกอบ) ที่คุณไม่ควรเข้าถึงโดยตรงโดยไม่ทำให้รหัสไม่สามารถใช้งานร่วมกับรุ่น Django ในอนาคตได้อีก


แน่นอนว่าคุณถูก จำกัด ไม่เพียง แต่ในกลุ่มฟรีดังนั้นลอง SQLAlchemy แทน Django ORM
Sławomir Lenart

5

มีโมดูลที่ให้คุณจัดกลุ่มโมเดล Django และยังคงใช้งาน QuerySet ได้ในผลลัพธ์: https://github.com/kako-nawao/django-group-by

ตัวอย่างเช่น:

from django_group_by import GroupByMixin

class BookQuerySet(QuerySet, GroupByMixin):
    pass

class Book(Model):
    title = TextField(...)
    author = ForeignKey(User, ...)
    shop = ForeignKey(Shop, ...)
    price = DecimalField(...)

class GroupedBookListView(PaginationMixin, ListView):
    template_name = 'book/books.html'
    model = Book
    paginate_by = 100

    def get_queryset(self):
        return Book.objects.group_by('title', 'author').annotate(
            shop_count=Count('shop'), price_avg=Avg('price')).order_by(
            'name', 'author').distinct()

    def get_context_data(self, **kwargs):
        return super().get_context_data(total_count=self.get_queryset().count(), **kwargs)

หนังสือ / books.html '

<ul>
{% for book in object_list %}
    <li>
        <h2>{{ book.title }}</td>
        <p>{{ book.author.last_name }}, {{ book.author.first_name }}</p>
        <p>{{ book.shop_count }}</p>
        <p>{{ book.price_avg }}</p>
    </li>
{% endfor %}
</ul>

ความแตกต่างกับการannotate/ aggregateคำสั่ง Django book.author.last_nameพื้นฐานคือการใช้คุณลักษณะของสาขาที่เกี่ยวข้องเช่นนั้น

หากคุณต้องการ PKs ของอินสแตนซ์ที่ถูกจัดกลุ่มเข้าด้วยกันให้เพิ่มคำอธิบายประกอบต่อไปนี้:

.annotate(pks=ArrayAgg('id'))

หมายเหตุ: ArrayAggเป็นฟังก์ชั่นเฉพาะของ Postgres ซึ่งมีให้ตั้งแต่ Django 1.9 เป็นต้นไป: https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


นี้Django กลุ่มโดยเป็นทางเลือกที่จะvaluesใช้วิธีการ ฉันคิดว่ามันมีจุดประสงค์ที่แตกต่าง
LShi

1
@LShi ไม่ใช่ทางเลือกค่านิยมแน่นอน valuesเป็น SQL selectในขณะที่group_byเป็น SQL group by(ตามชื่อบ่งชี้ ... ) ทำไมต้องลงคะแนน? เรากำลังใช้รหัสดังกล่าวในการผลิตเพื่อนำgroup_byคำสั่งที่ซับซ้อนไปใช้
Risadinha

มันdocกล่าวว่าgroup_by"พฤติกรรมส่วนใหญ่ชอบวิธีการที่ค่า แต่มีหนึ่งความแตกต่าง ..." ในเอกสารไม่ได้พูดถึง SQL GROUP BYและกรณีการใช้งานที่จะให้ไม่ได้บอกว่ามันมีอะไรจะทำอย่างไรกับ GROUP BYSQL ฉันจะถอนการลงคะแนนเมื่อมีคนทำเรื่องนี้ให้ชัดเจน แต่หมอนั่นทำให้เข้าใจผิดจริงๆ
LShi

หลังจากอ่านเอกสารสำหรับvaluesฉันพบว่าฉันพลาดที่valuesตัวเองทำงานเหมือน GROUP BY มันเป็นความผิดของฉัน ฉันคิดว่าการใช้งานง่ายitertools.groupbyกว่า django-group-by เมื่อvaluesไม่เพียงพอ
LShi

1
มันเป็นไปไม่ได้ที่จะทำgroup byจากด้านบนด้วยการvaluesโทรง่าย ๆด้วยหรือไม่annotateและไม่ดึงทุกอย่างจากฐานข้อมูล ข้อเสนอแนะของคุณitertools.groupbyสำหรับชุดข้อมูลขนาดเล็ก แต่ไม่ใช่สำหรับชุดข้อมูลหลายพันชุดที่คุณอาจต้องการหน้า แน่นอนว่า ณ จุดนี้คุณจะต้องคิดถึงดัชนีการค้นหาพิเศษที่มีข้อมูลที่จัดเตรียมไว้ (จัดกลุ่มแล้ว) อยู่ดี
Risadinha

0

เอกสารบอกว่าคุณสามารถใช้ค่าไปยังกลุ่ม queryset

class Travel(models.Model):
    interest = models.ForeignKey(Interest)
    user = models.ForeignKey(User)
    time = models.DateTimeField(auto_now_add=True)

# Find the travel and group by the interest:

>>> Travel.objects.values('interest').annotate(Count('user'))
<QuerySet [{'interest': 5, 'user__count': 2}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited for 2 times, 
# and the interest(id=6) had only been visited for 1 time.

>>> Travel.objects.values('interest').annotate(Count('user', distinct=True)) 
<QuerySet [{'interest': 5, 'user__count': 1}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited by only one person (but this person had 
#  visited the interest for 2 times

คุณสามารถค้นหาหนังสือทั้งหมดและจัดกลุ่มตามชื่อโดยใช้รหัสนี้:

Book.objects.values('name').annotate(Count('id')).order_by() # ensure you add the order_by()

คุณสามารถดูแผ่น cheet บางที่นี่


-1

หากฉันไม่เข้าใจผิดว่าคุณสามารถใช้อะไรก็ได้แบบสอบถาม - set .group_by = [' field ']


8
นี่ไม่ใช่กรณีอย่างน้อยใน Django 1.6: วัตถุ 'QuerySet' ไม่มีแอตทริบิวต์ 'group_by'
Facundo Olano

1
การใช้งานที่เหมาะสมอาจเป็น queryset.query.group_by = [... ] แต่สิ่งนี้จะทำลายซีแมนทิกส์ของเคียวรีและไม่ทำงานตามที่คาดไว้
Luis Masuelli

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.