Django tương đương cho số lượng và nhóm theo


91

Tôi có một mô hình trông như thế này:

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

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

Tôi muốn chọn số lượng (chỉ số lượng) các mục cho mỗi danh mục, vì vậy trong SQL nó sẽ đơn giản như sau:

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

Có cách nào tương đương với việc làm này "theo cách Django" không? Hay SQL thuần túy là lựa chọn duy nhất? Tôi quen thuộc với phương thức count () trong Django, tuy nhiên tôi không thấy cách nhóm theo sẽ phù hợp ở đó.



@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 đây là bản sao? câu hỏi này đã được hỏi vào năm 2008, và câu hỏi mà bạn đang đề cập là 2 năm sau.
Sergey Golovchenko

Sự đồng thuận hiện tại là kết thúc bằng "chất lượng": < meta.stackexchange.com/questions/147643/… > Vì "chất lượng" không thể đo lường được, tôi chỉ đi theo lượt ủng hộ. ;-) Có khả năng là câu hỏi nào đánh trúng các từ khóa Google dành cho người mới bắt đầu tốt nhất trên tiêu đề.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Câu trả lời:


131

Đây, như tôi vừa khám phá, là cách thực hiện việc này với API tổng hợp Django 1.1:

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

3
như hầu hết mọi thứ trong Django, không ai trong số này khá có ý nghĩa để xem nhưng (không giống như hầu hết mọi thứ trong Django) một lần tôi thực sự cố gắng nó, nó thật tuyệt vời: P
JSH

3
lưu ý rằng bạn cần sử dụng order_by()nếu 'category'không phải là thứ tự mặc định. (Xem câu trả lời toàn diện hơn của Daniel.)
Rick Westera

Lý do tại sao điều này hoạt động là vì .annotate()hoạt động hơi khác sau một.values() : "Tuy nhiên, khi mệnh đề giá trị () được sử dụng để ràng buộc các cột được trả về trong tập kết quả, phương pháp đánh giá chú thích hơi khác một chút. Thay vì trả về một chú thích kết quả cho mỗi kết quả trong QuerySet ban đầu, các kết quả ban đầu được nhóm theo các kết hợp duy nhất của các trường được chỉ định trong mệnh đề giá trị (). "
mgalgs

58

( Cập nhật : Hỗ trợ tổng hợp ORM đầy đủ hiện được bao gồm trong Django 1.1 . Đúng như cảnh báo bên dưới về việc sử dụng các API riêng tư, phương pháp được ghi ở đây không còn hoạt động trong các phiên bản Django sau 1.1. Tôi chưa tìm hiểu lý do tại sao; nếu bạn đang ở phiên bản 1.1 trở lên, bạn vẫn nên sử dụng API tổng hợp thực .)

Hỗ trợ tổng hợp cốt lõi đã có trong phiên bản 1.0; nó chỉ là không có giấy tờ, không được hỗ trợ và chưa có một API thân thiện. Nhưng đây là cách bạn có thể sử dụng nó cho đến khi 1.1 đến (bạn có thể tự chịu rủi ro và hoàn toàn biết rằng thuộc tính query.group_by không phải là một phần của API công khai và có thể thay đổi):

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

Sau đó, nếu bạn lặp lại query_set, mỗi giá trị trả về sẽ là một từ điển với khóa "danh mục" và khóa "đếm".

Bạn không cần phải sắp xếp theo -count ở đây, nó chỉ được bao gồm để chứng minh cách nó được thực hiện (nó phải được thực hiện trong lệnh gọi .extra (), không phải ở nơi khác trong chuỗi xây dựng queryset). Ngoài ra, bạn cũng có thể nói count (id) thay vì count (1), nhưng sau này có thể hiệu quả hơn.

Cũng lưu ý rằng khi đặt .query.group_by, các giá trị phải là tên cột DB thực ('category_id') chứ không phải tên trường Django ('category'). Điều này là do bạn đang điều chỉnh nội bộ truy vấn ở mức mà mọi thứ đều theo điều kiện DB, không phải điều khoản Django.


+1 cho phương pháp cũ. Ngay cả khi hiện tại không được hỗ trợ, thật kỳ diệu để nói rằng ít nhất. Thật tuyệt vời.
oanh tạc

Hãy xem API tổng hợp Django tại docs.djangoproject.com/en/dev/topics/db/aggregation/… có thể thực hiện các tác vụ phức tạp khác với nó, ở đó bạn sẽ tìm thấy một số ví dụ mạnh mẽ.
serfer

@ serfer2 vâng, những tài liệu đó đã được liên kết từ đầu câu trả lời này.
Carl Meyer

56

Vì tôi hơi bối rối về cách nhóm trong Django 1.1 hoạt động nên tôi nghĩ rằng tôi sẽ giải thích ở đây về cách bạn sử dụng nó một cách chính xác. Đầu tiên, để lặp lại những gì Michael đã nói:

Đây, như tôi vừa khám phá, là cách thực hiện việc này với API tổng hợp Django 1.1:

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

Cũng lưu ý rằng bạn cần phải from django.db.models import Count!

Thao tác này sẽ chỉ chọn các danh mục và sau đó thêm một chú thích được gọi category__count. Tùy thuộc vào thứ tự mặc định, đây có thể là tất cả những gì bạn cần, nhưng nếu thứ tự mặc định sử dụng trường khác với trường categorynày sẽ không hoạt động . Lý do cho điều này là các trường bắt buộc để đặt hàng cũng được chọn và làm cho mỗi hàng là duy nhất, vì vậy bạn sẽ không bị nhóm lại theo cách bạn muốn. Một cách nhanh chóng để khắc phục điều này là đặt lại thứ tự:

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

Điều này sẽ tạo ra chính xác kết quả bạn muốn. Để đặt tên của chú thích, bạn có thể sử dụng:

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

Sau đó, bạn sẽ có một chú thích được gọi mycounttrong kết quả.

Mọi thứ khác về phân nhóm đều rất đơn giản với tôi. Hãy nhớ xem API tổng hợp Django để biết thêm thông tin chi tiết.


1
. để thực hiện cùng một bộ hành động trên Item.objects.values lĩnh vực trọng điểm nước ngoài ( 'category__category') chú thích (Đếm ( 'category__category')) order_by ().
Mutant

Làm cách nào để xác định trường đặt hàng mặc định là gì?
Bogatyr

2

Cái này thế nào? (Khác với chậm.)

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

Nó có lợi thế là ngắn, ngay cả khi nó lấy rất nhiều hàng.


Biên tập.

Phiên bản truy vấn duy nhất. BTW, điều này thường nhanh hơn SELECT COUNT (*) trong cơ sở dữ liệu. Hãy thử nó để xem.

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

Nó rất hay và ngắn gọn, tuy nhiên tôi muốn tránh có một lệnh gọi cơ sở dữ liệu riêng biệt cho từng danh mục.
Sergey Golovchenko

Đây là một cách tiếp cận thực sự tốt cho các trường hợp đơn giản. Nó giảm xuống khi bạn có một tập dữ liệu lớn và bạn muốn sắp xếp + giới hạn (tức là phân trang) theo số lượng, mà không kéo xuống hàng tấn dữ liệu không cần thiết.
Carl Meyer

@Carl Meyer: Đúng - nó có thể khó hiểu đối với một tập dữ liệu lớn; Tuy nhiên, bạn cần phải làm điểm chuẩn để chắc chắn về điều đó. Ngoài ra, nó cũng không dựa vào những thứ không được hỗ trợ; nó hoạt động tạm thời cho đến khi các tính năng không được hỗ trợ được hỗ trợ.
S.Lott
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.