Django เทียบเท่าสำหรับการนับและจัดกลุ่มตาม


91

ฉันมีโมเดลที่มีลักษณะดังนี้:

class Category(models.Model):
    name = models.CharField(max_length=60)

class Item(models.Model):
    name = models.CharField(max_length=60)
    category = models.ForeignKey(Category)

ฉันต้องการเลือกจำนวน (เฉพาะจำนวน) ของรายการสำหรับแต่ละหมวดหมู่ดังนั้นใน SQL จะง่ายดังนี้:

select category_id, count(id) from item group by category_id

มีวิธีเทียบเท่ากับการทำ "ทาง Django" หรือไม่? หรือ SQL ธรรมดาเป็นตัวเลือกเดียว? ฉันคุ้นเคยกับวิธีการcount ()ใน Django แต่ฉันไม่เห็นว่ากลุ่มตามจะเหมาะสมกับที่นั่นอย่างไร



@CiroSantilli 巴拿馬文件六四事件法轮功มันซ้ำกันยังไง? คำถามนี้ถูกถามในปี 2008 และคำถามที่คุณอ้างถึงคือ 2 ปีต่อมา
Sergey Golovchenko

ฉันต้องปิดด้วย "คุณภาพ": < meta.stackexchange.com/questions/147643/… > เนื่องจาก "คุณภาพ" ไม่สามารถวัดผลได้ฉันก็แค่เพิ่มคะแนน ;-) เป็นไปได้ว่าคำถามใดที่มีคำหลัก Google สำหรับมือใหม่ที่ดีที่สุดในชื่อ
Ciro Santilli 郝海东冠状病六四事件

คำตอบ:


132

ตามที่ฉันเพิ่งค้นพบคือวิธีดำเนินการกับ Django 1.1 aggregation API:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

3
เช่นเดียวกับสิ่งส่วนใหญ่ใน Django ไม่มีสิ่งใดที่สมเหตุสมผลที่จะดู แต่ (ไม่เหมือนกับสิ่งต่างๆใน Django) เมื่อฉันได้ลองใช้จริงมันยอดเยี่ยมมาก: P
jsh

3
โปรดทราบว่าคุณต้องใช้order_by()ถ้า'category'ไม่ใช่คำสั่งเริ่มต้น (ดูคำตอบที่ครอบคลุมมากขึ้นของ Daniel)
Rick Westera

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

58

( อัปเดต : ตอนนี้การสนับสนุนการรวม ORM แบบเต็มรวมอยู่ในDjango 1.1จริงตามคำเตือนด้านล่างเกี่ยวกับการใช้ API ส่วนตัววิธีการที่บันทึกไว้ที่นี่ไม่สามารถใช้งานได้ใน Django เวอร์ชันหลัง 1.1 อีกต่อไปฉันไม่ได้ขุดเพื่อหาสาเหตุ หากคุณใช้ 1.1 หรือใหม่กว่าคุณควรใช้API การรวมจริงอยู่ดี)

การสนับสนุนการรวมหลักมีอยู่แล้วใน 1.0; มันเป็นเพียงเอกสารที่ไม่ได้รับการสนับสนุนและยังไม่มี API ที่เป็นมิตรอยู่ด้านบน แต่นี่คือวิธีที่คุณสามารถใช้งานได้จนกว่า 1.1 จะมาถึง (ยอมรับความเสี่ยงของคุณเองและด้วยความรู้ทั้งหมดว่าแอตทริบิวต์ query.group_by ไม่ได้เป็นส่วนหนึ่งของ API สาธารณะและสามารถเปลี่ยนแปลงได้):

query_set = Item.objects.extra(select={'count': 'count(1)'}, 
                               order_by=['-count']).values('count', 'category')
query_set.query.group_by = ['category_id']

หากคุณวนซ้ำใน query_set ค่าที่ส่งคืนแต่ละค่าจะเป็นพจนานุกรมที่มีคีย์ "หมวดหมู่" และคีย์ "count"

คุณไม่จำเป็นต้องสั่งซื้อโดย -count ที่นี่ซึ่งรวมไว้เพื่อแสดงให้เห็นว่ามันทำอย่างไร (ต้องทำในการเรียก. extra () ไม่ใช่ที่อื่นในห่วงโซ่การก่อสร้างแบบสอบถาม) นอกจากนี้คุณสามารถพูด count (id) แทน count (1) ได้เช่นกัน แต่ข้อหลังอาจมีประสิทธิภาพมากกว่า

โปรดทราบว่าเมื่อตั้งค่า .query.group_by ค่าต้องเป็นชื่อคอลัมน์ DB จริง ('category_id') ไม่ใช่ชื่อฟิลด์ Django ('category') เนื่องจากคุณกำลังปรับแต่งการสืบค้นภายในในระดับที่ทุกอย่างอยู่ในเงื่อนไข DB ไม่ใช่เงื่อนไขของ Django


+1 สำหรับวิธีการเดิม แม้ว่าจะยังไม่ได้รับการสนับสนุนในขณะนี้ แต่ก็เป็นเรื่องที่พูดได้อย่างชัดเจน น่าทึ่งจริงๆ
airstrike

ดูที่ Django aggregation API ที่docs.djangoproject.com/en/dev/topics/db/aggregation/…งานที่ซับซ้อนอื่น ๆ สามารถทำได้ด้วยคุณจะพบตัวอย่างที่มีประสิทธิภาพ
serfer2

@ serfer2 ใช่เอกสารเหล่านั้นเชื่อมโยงจากด้านบนของคำตอบนี้แล้ว
Carl Meyer

56

เนื่องจากฉันสับสนเล็กน้อยเกี่ยวกับวิธีการจัดกลุ่มใน Django 1.1 ฉันคิดว่าฉันจะอธิบายรายละเอียดที่นี่ว่าคุณใช้มันอย่างไร ก่อนอื่นให้ทำซ้ำสิ่งที่ไมเคิลพูด:

ตามที่ฉันเพิ่งค้นพบคือวิธีดำเนินการกับ Django 1.1 aggregation API:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

โปรดทราบว่าคุณต้องfrom django.db.models import Count!

category__countนี้จะเลือกเฉพาะหมวดหมู่และเพิ่มคำอธิบายประกอบที่เรียกว่า ทั้งนี้ขึ้นอยู่กับการสั่งซื้อเริ่มต้นนี้อาจจะเป็นสิ่งที่คุณต้องแต่ถ้าสั่งซื้อเริ่มต้นใช้สนามอื่น ๆ กว่าcategoryนี้จะไม่ทำงาน ด้วยเหตุนี้จึงมีการเลือกฟิลด์ที่จำเป็นสำหรับการสั่งซื้อและทำให้แต่ละแถวไม่ซ้ำกันดังนั้นคุณจะไม่ได้รับการจัดกลุ่มตามที่คุณต้องการ วิธีหนึ่งที่รวดเร็วในการแก้ไขปัญหานี้คือรีเซ็ตการสั่งซื้อ:

Item.objects.values('category').annotate(Count('category')).order_by()

สิ่งนี้ควรให้ผลลัพธ์ที่คุณต้องการ ในการตั้งชื่อคำอธิบายประกอบคุณสามารถใช้:

...annotate(mycount = Count('category'))...

จากนั้นคุณจะมีคำอธิบายประกอบที่เรียกmycountในผลลัพธ์

ทุกอย่างเกี่ยวกับการจัดกลุ่มนั้นตรงไปตรงมาสำหรับฉันมาก อย่าลืมตรวจสอบDjango aggregation APIสำหรับข้อมูลโดยละเอียดเพิ่มเติม


1
เพื่อดำเนินการชุดเดียวกันกับฟิลด์คีย์ต่างประเทศ Item.objects.values ​​('category__category'). annotate (Count ('category__category')) order_by ()
Mutant

เราจะพิจารณาได้อย่างไรว่าฟิลด์การสั่งซื้อเริ่มต้นคืออะไร?
Bogatyr

2

วิธีนี้เป็นอย่างไร (นอกเหนือจากช้า)

counts= [ (c, Item.filter( category=c.id ).count()) for c in Category.objects.all() ]

มีข้อดีคือสั้นแม้ว่าจะดึงข้อมูลจำนวนแถวได้มากก็ตาม


แก้ไข.

เวอร์ชันแบบสอบถามเดียว BTW มักเร็วกว่า SELECT COUNT (*) ในฐานข้อมูล ลองไปดู.

counts = defaultdict(int)
for i in Item.objects.all():
    counts[i.category] += 1

เป็นเรื่องที่ดีและสั้น แต่ฉันต้องการหลีกเลี่ยงการเรียกฐานข้อมูลแยกต่างหากสำหรับแต่ละหมวดหมู่
Sergey Golovchenko

นี่เป็นแนวทางที่ดีมากสำหรับกรณีง่ายๆ จะตกลงมาเมื่อคุณมีชุดข้อมูลขนาดใหญ่และคุณต้องการสั่งซื้อ + ขีด จำกัด (เช่นเลขหน้า) ตามจำนวนโดยไม่ต้องดึงข้อมูลจำนวนมากที่ไม่จำเป็นออกไป
Carl Meyer

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