วิธีการเลือก COUNT (*) GROUP BY และ ORDER BY ใน Django


99

ฉันใช้แบบจำลองธุรกรรมเพื่อติดตามเหตุการณ์ทั้งหมดที่เกิดขึ้นในระบบ

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

ฉันจะได้นักแสดง 5 อันดับแรกในระบบของฉันได้อย่างไร

ใน sql โดยทั่วไปจะเป็น

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC

คำตอบ:


181

ตามเอกสารคุณควรใช้:

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

ค่า (): ระบุว่าจะใช้คอลัมน์ใดในการ "จัดกลุ่มตาม"

เอกสาร Django:

"เมื่อใช้คำสั่ง values ​​() เพื่อ จำกัด คอลัมน์ที่ส่งคืนในชุดผลลัพธ์วิธีการประเมินคำอธิบายประกอบจะแตกต่างกันเล็กน้อยแทนที่จะส่งคืนผลลัพธ์ที่มีคำอธิบายประกอบสำหรับแต่ละผลลัพธ์ใน QuerySet เดิมผลลัพธ์ดั้งเดิมจะถูกจัดกลุ่มตาม ไปยังชุดค่าผสมที่ไม่ซ้ำกันของฟิลด์ที่ระบุในส่วนคำสั่ง values ​​() "

annotate (): ระบุการดำเนินการกับค่าที่จัดกลุ่ม

เอกสาร Django:

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

การสรุปต่อออบเจ็กต์สามารถสร้างได้โดยใช้อนุประโยค annotate () เมื่อมีการระบุอนุประโยค annotate () แต่ละอ็อบเจ็กต์ใน QuerySet จะถูกใส่คำอธิบายประกอบด้วยค่าที่ระบุ

ลำดับตามข้อเป็นตัวอธิบาย

สรุป: คุณจัดกลุ่มโดยสร้างชุดข้อมูลผู้เขียนเพิ่มคำอธิบายประกอบ (ซึ่งจะเพิ่มช่องพิเศษให้กับค่าที่ส่งคืน) และสุดท้ายคุณเรียงลำดับตามค่านี้

อ้างอิงhttps://docs.djangoproject.com/en/dev/topics/db/aggregation/สำหรับข้อมูลเชิงลึกเพิ่มเติม

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

Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')
Transaction.objects.all().values('actor').annotate(total=Count('id')).order_by('total')

สำหรับฉันมันใช้งานTransaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')ได้อย่าลืมนำเข้า Count จาก django.db.models ขอบคุณ
Ivancho

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

คุณยังสามารถใช้สิ่งนี้สำหรับชุดแบบสอบถามผลการค้นหา postgres เพื่อรับ faceting!
yekta

2
@kronosapiens มันมีผลกับมันอย่างน้อยทุกวันนี้ (ฉันใช้ Django 2.1.4) ในตัวอย่างtotalคือชื่อที่กำหนดและจำนวนที่ใช้ใน sql COUNT('actor')ซึ่งในกรณีนี้ไม่สำคัญ แต่ถ้าเช่นvalues('x', 'y').annotate(count=Count('x'))คุณจะได้รับCOUNT(x)ไม่ใช่COUNT(*)หรือCOUNT(x, y)เพิ่งลองใช้./manage.py shell
timdiels

35

เช่นเดียวกับที่ @Alvaro ได้ตอบคำสั่งเทียบเท่าโดยตรงของ Django GROUP BY:

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

ผ่านการใช้งานvalues()และannotate()วิธีการดังต่อไปนี้:

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

อย่างไรก็ตามต้องชี้ให้เห็นอีกสิ่งหนึ่ง:

ถ้าแบบมีการสั่งซื้อเริ่มต้นที่กำหนดไว้ในclass Metaที่.order_by()ข้อเป็นหน้าที่เพื่อให้ได้ผลลัพธ์ที่เหมาะสม คุณไม่สามารถข้ามได้แม้ว่าจะไม่มีการสั่งซื้อก็ตาม

นอกจากนี้สำหรับรหัสคุณภาพสูงขอแนะนำให้ใส่.order_by()ประโยคหลังannotate()เสมอแม้ว่าจะไม่มีclass Meta: orderingก็ตาม วิธีการดังกล่าวจะทำให้คำสั่งหลักฐานในอนาคตมันจะทำงานได้ตามที่ตั้งใจไว้โดยไม่คำนึงถึงอนาคตการเปลี่ยนแปลงใด ๆ class Meta: orderingที่จะ


ให้ฉันเป็นตัวอย่าง หากโมเดลมี:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

จากนั้นแนวทางดังกล่าวจะไม่ได้ผล:

Transaction.objects.values('actor').annotate(total=Count('actor'))

นั่นเป็นเพราะ Django ดำเนินการเพิ่มเติมGROUP BYในทุกสนามในclass Meta: ordering

หากคุณจะพิมพ์แบบสอบถาม:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

จะเป็นที่ชัดเจนว่าการรวมจะไม่ทำงานตามที่ตั้งใจไว้ดังนั้นจึง.order_by()ต้องใช้อนุประโยคเพื่อล้างพฤติกรรมนี้และรับผลลัพธ์การรวมที่เหมาะสม

ดู: การโต้ตอบกับการสั่งซื้อเริ่มต้นหรือ order_by ()ในเอกสาร Django อย่างเป็นทางการ


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