Mam model, który wygląda tak:
class Category(models.Model):
name = models.CharField(max_length=60)
class Item(models.Model):
name = models.CharField(max_length=60)
category = models.ForeignKey(Category)
Chcę wybrać liczbę (tylko liczbę) elementów dla każdej kategorii, więc w SQL byłoby to tak proste:
select category_id, count(id) from item group by category_id
Czy istnieje odpowiednik robienia tego „w sposób Django”? A może zwykły SQL jest jedyną opcją? Znam metodę count () w Django, jednak nie widzę, jak by tam pasowało group by.
Odpowiedzi:
Oto, jak właśnie odkryłem, jak to zrobić za pomocą interfejsu API agregacji Django 1.1:
from django.db.models import Count theanswer = Item.objects.values('category').annotate(Count('category'))
źródło
order_by()
jeśli'category'
nie jest to domyślna kolejność. (Zobacz bardziej wyczerpującą odpowiedź Daniela.).annotate()
działa nieco inaczej po a.values()
: „Jednak gdy klauzula values () jest używana do ograniczenia kolumn zwracanych w zestawie wyników, metoda oceny adnotacji jest nieco inna. Zamiast zwracać adnotację wynik dla każdego wyniku w oryginalnym zestawie QuerySet, oryginalne wyniki są grupowane według unikalnych kombinacji pól określonych w klauzuli values (). "( Aktualizacja : Pełna obsługa agregacji ORM jest teraz zawarta w Django 1.1 . Zgodnie z poniższym ostrzeżeniem o używaniu prywatnych API, udokumentowana tutaj metoda nie działa już w wersjach Django po 1.1. jeśli używasz wersji 1.1 lub nowszej, i tak powinieneś użyć prawdziwego interfejsu API agregacji ).
Podstawowe wsparcie agregacji było już dostępne w wersji 1.0; jest po prostu nieudokumentowany, nieobsługiwany i nie ma jeszcze przyjaznego interfejsu API. Ale oto, jak możesz z niego korzystać, dopóki nie nadejdzie 1.1 (na własne ryzyko i mając pełną świadomość, że atrybut query.group_by nie jest częścią publicznego interfejsu API i może się zmienić):
query_set = Item.objects.extra(select={'count': 'count(1)'}, order_by=['-count']).values('count', 'category') query_set.query.group_by = ['category_id']
Jeśli następnie wykonasz iterację po query_set, każda zwrócona wartość będzie słownikiem z kluczem „category” i kluczem „count”.
Nie musisz tu zamawiać według -count, jest to dołączone tylko po to, aby zademonstrować, jak to się robi (musi to być zrobione w wywołaniu .extra (), a nie gdzie indziej w łańcuchu konstrukcji queryset). Równie dobrze możesz powiedzieć count (id) zamiast count (1), ale to drugie może być bardziej wydajne.
Zauważ również, że podczas ustawiania .query.group_by, wartości muszą być rzeczywistymi nazwami kolumn DB („category_id”), a nie nazwami pól Django („kategoria”). Dzieje się tak, ponieważ dostosowujesz wewnętrzne kwerendy na poziomie, na którym wszystko jest w terminach DB, a nie w terminach Django.
źródło
Ponieważ byłem trochę zdezorientowany, jak działa grupowanie w Django 1.1, pomyślałem, że opowiem tutaj o tym, jak dokładnie go używasz. Po pierwsze, powtórzyć to, co powiedział Michael:
Pamiętaj też, że musisz
from django.db.models import Count
!Spowoduje to wybranie tylko kategorii, a następnie doda adnotację o nazwie
category__count
. W zależności od kolejności domyślnej może to być wszystko, czego potrzebujesz, ale jeśli w kolejności domyślnej używane jest inne polecategory
, nie zadziała . Powodem tego jest to, że pola wymagane do zamówienia są również zaznaczone i sprawiają, że każdy wiersz jest unikalny, więc nie otrzymasz pogrupowanych rzeczy tak, jak chcesz. Szybkim sposobem rozwiązania tego problemu jest zresetowanie kolejności:Item.objects.values('category').annotate(Count('category')).order_by()
Powinno to przynieść dokładnie takie rezultaty, jakie chcesz. Aby ustawić nazwę adnotacji, możesz użyć:
...annotate(mycount = Count('category'))...
Następnie otrzymasz adnotację wywołaną
mycount
w wynikach.Wszystko inne związane z grupowaniem było dla mnie bardzo proste. Aby uzyskać bardziej szczegółowe informacje, sprawdź interfejs API agregacji Django .
źródło
Jak to jest? (Inaczej niż wolno.)
counts= [ (c, Item.filter( category=c.id ).count()) for c in Category.objects.all() ]
Ma tę zaletę, że jest krótki, nawet jeśli pobiera wiele wierszy.
Edytować.
Wersja z jednym zapytaniem. BTW, jest to często szybsze niż SELECT COUNT (*) w bazie danych. Spróbuj to zobaczyć.
counts = defaultdict(int) for i in Item.objects.all(): counts[i.category] += 1
źródło