Jak wykonać zapytanie jako GROUP BY w django?


332

Pytam o model:

Members.objects.all()

I zwraca:

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

Chcę poznać najlepszy sposób Django na uruchomienie group_byzapytania do mojej bazy danych, na przykład:

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

Co oczywiście nie działa. Wiem, że możemy zrobić kilka sztuczek django/db/models/query.py, ale jestem ciekawy, jak to zrobić bez łatania.

Odpowiedzi:


483

Jeśli chcesz wykonać agregację, możesz użyć funkcji agregacji ORM :

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

Powoduje to zapytanie podobne do

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

i wynik miałby formę

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

6
@Harry: Możesz to połączyć. Coś w stylu:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
Eli

57
mam pytanie, to zapytanie zwraca tylko oznaczenie i dcount, co jeśli chcę uzyskać inne wartości z tabeli?
AJ

19
Zauważ, że jeśli twoje sortowanie jest polem innym niż oznaczenie, nie będzie działać bez resetowania sortowania. Zobacz stackoverflow.com/a/1341667/202137
Gidgidonihah

12
@Gidgidonihah Prawda, przykład powinien brzmieć Members.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix

7
mam pytanie, to zapytanie zwraca tylko oznaczenie i dcount, co jeśli chcę uzyskać inne wartości z tabeli?
Yann 叶

55

Prostym rozwiązaniem, ale nie właściwym sposobem jest użycie surowego SQL :

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

Innym rozwiązaniem jest użycie group_bywłaściwości:

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

Możesz teraz iterować zmienną wyników, aby pobrać wyniki. Zauważ, żegroup_by nie jest to udokumentowane i może zostać zmienione w przyszłej wersji Django.

I ... dlaczego chcesz użyć group_by ? Jeśli nie używasz agregacji, możesz użyć jej order_bydo osiągnięcia podobnego wyniku.


Czy możesz mi powiedzieć, jak to zrobić za pomocą order_by?
prostu szorstki

2
Cześć, jeśli nie używasz agregacji, możesz emulować group_by za pomocą order_by i wyeliminować niepotrzebne wpisy. Oczywiście jest to emulacja i można jej używać tylko wtedy, gdy nie używa się dużej ilości danych. Ponieważ nie mówił o agregacji, pomyślałem, że to może być rozwiązanie.
Michael

Hej to jest super - można wyjaśnić, jak do wykorzystania execute_sql nie wydaje się do pracy ..
rh0dium

8
Zauważ, że to już nie działa w Django 1.9. stackoverflow.com/questions/35558120/…
grokpot

1
Jest to rodzaj hackerskiego sposobu korzystania z ORM. Nie powinieneś ręcznie tworzyć nowych zestawów zapytań przekazujących stare.
Ian Kirkpatrick

32

Możesz także użyć regroup znacznika szablonu do grupowania według atrybutów. Z dokumentów:

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>

Wygląda tak:

  • Indie
    • Bombaj: 19 000 000
    • Kalkuta: 15 000 000
  • USA
    • Nowy Jork: 20 000 000
    • Chicago: 7 000 000
  • Japonia
    • Tokio: 33 000 000

Działa również na QuerySet s wierzę.

źródło: https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#regroup

edytuj: zwróć uwagę, że regrouptag nie działa tak, jak byś tego oczekiwał, jeśli lista słowników nie jest posortowana według klucza. Działa iteracyjnie. Więc posortuj listę (lub zestaw zapytań) według klucza grupującego przed przekazaniem go do regroupznacznika.


1
To jest doskonałe! Dużo szukałem prostego sposobu, aby to zrobić. Działa to również na zestawach zapytań, tak to wykorzystałem.
CarmenA

1
jest to całkowicie błędne, jeśli czytasz z bazy danych duży zestaw danych, a następnie po prostu używasz wartości zagregowanych.
Sławomir Lenart

@ SławomirLenart na pewno, to może nie być tak wydajne jak proste zapytanie DB. Ale dla prostych przypadków użycia może to być dobre rozwiązanie
inostia

Działa to, jeśli wynik pokazany w szablonie. Ale w przypadku JsonResponse lub innej odpowiedzi pośredniej. to rozwiązanie nie będzie działać.
Willy satrio nugroho,

1
@Willysatrionugroho, jeśli chcesz to zrobić w widoku, na przykład stackoverflow.com/questions/477820/... może dla Ciebie działać
inostia

7

Musisz wykonać niestandardowy kod SQL zgodnie z przykładem w tym fragmencie:

Niestandardowy SQL za pomocą podzapytania

Lub w niestandardowym menedżerze, jak pokazano w internetowych dokumentach Django:

Dodanie dodatkowych metod menedżera


1
Rodzaj rozwiązania w obie strony. Użyłbym tego, gdybym miał z tego szersze zastosowanie. Ale tutaj potrzebuję tylko liczby członków na oznaczenie, to wszystko.
prostu szorstki

Nie ma problemu. Myślałem o wspominaniu o funkcjach agregacji 1.1, ale założyłem, że korzystasz z wersji Release :)
Van Gale,

Chodzi o stosowanie surowych zapytań, które pokazują słabość ORM Django.
Sławomir Lenart

5

Django nie obsługuje darmowej grupy według zapytań . Nauczyłem się tego w bardzo zły sposób. ORM nie jest zaprojektowany do obsługi takich rzeczy, jak to, co chcesz robić, bez użycia niestandardowego SQL. Jesteś ograniczony do:

  • RAW sql (tj. MyModel.objects.raw ())
  • cr.execute zdania (i ręcznie wykonane parsowanie wyniku).
  • .annotate() (grupa według zdań wykonywana jest w modelu potomnym dla .annotate (), w przykładach takich jak agregacja lines_count = Count ('lines'))).

Za pomocą zestawu zapytań qsmożesz wywoływać, qs.query.group_by = ['field1', 'field2', ...]ale ryzykowne jest, jeśli nie wiesz, które zapytanie edytujesz i nie masz gwarancji, że zadziała, a nie uszkodzi wewnętrznych elementów obiektu QuerySet. Poza tym jest to wewnętrzny (nieudokumentowany) interfejs API, do którego nie należy uzyskiwać bezpośredniego dostępu bez ryzyka, że ​​kod nie będzie już zgodny z przyszłymi wersjami Django.


w rzeczywistości jesteś ograniczony nie tylko darmowym grupowaniem, więc wypróbuj SQLAlchemy zamiast Django ORM.
Sławomir Lenart

5

Istnieje moduł, który pozwala grupować modele Django i nadal pracować z zestawem QuerySet w wyniku: https://github.com/kako-nawao/django-group-by

Na przykład:

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)

„book / 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>

Różnica w stosunku do annotate/ aggregatepodstawowych zapytań Django polega na użyciu atrybutów powiązanego pola, np book.author.last_name.

Jeśli potrzebujesz PK instancji, które zostały zgrupowane razem, dodaj następującą adnotację:

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

UWAGA: ArrayAggjest funkcją specyficzną dla Postgres, dostępną od Django 1.9: https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


Ten django-group-by jest alternatywą dla valuesmetody. Myślę, że jest w innym celu.
LShi

1
@LShi Nie jest to alternatywa dla wartości, oczywiście że nie. valuesjest SQL, selectpodczas gdy group_byjest SQL group by(jak sama nazwa wskazuje ...). Dlaczego głosowanie negatywne? Używamy takiego kodu w produkcji do implementacji złożonych group_byinstrukcji.
Risadinha,

Jego dokument mówi: group_by„zachowuje się głównie jak metoda wartości, ale z jedną różnicą ...” Dokument nie wspomina o SQL, GROUP BYa podany przypadek użycia nie sugeruje, że ma to coś wspólnego z SQL GROUP BY. Odwołam głosowanie w dół, gdy ktoś to wyjaśni, ale ten dokument naprawdę wprowadza w błąd.
LShi

Po przeczytaniu dokumentu dlavalues , zauważyłem, że przegapiłem, że valuessamo to działa jak GROUP BY. To moja wina. Myślę, że jest prostszy w użyciu itertools.groupbyniż ten django-group-by, gdy valuesjest niewystarczający.
LShi

1
Niemożliwe jest wykonanie group bypowyższej czynności za pomocą zwykłego valuespołączenia - z lub annotatebez pobierania i bez pobierania wszystkiego z bazy danych. Twoja sugestia itertools.groupbydziałania dla małych zestawów danych, ale nie dla kilku tysięcy zestawów danych, które prawdopodobnie chcesz na stronie. Oczywiście w tym momencie będziesz musiał pomyśleć o specjalnym indeksie wyszukiwania, który i tak zawiera przygotowane (już zgrupowane) dane.
Risadinha

0

Dokument mówi, że można użyć wartości do grupy z 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

Możesz znaleźć wszystkie książki i pogrupować je według nazwy, używając tego kodu:

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

Można pooglądać arkusz cheet tutaj .


-1

Jeśli się nie mylę, możesz użyć dowolnego zestawu zapytań .group_by = [' field ']


8
Tak nie jest, przynajmniej w Django 1.6: Obiekt „QuerySet” nie ma atrybutu „group_by”
Facundo Olano

1
Prawidłowym zastosowaniem może być queryset.query.group_by = [...], ale spowodowałoby to przerwanie semantyki zapytania i nie działałoby zgodnie z oczekiwaniami.
Luis Masuelli,

-2
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

najpierw musisz zaimportować Suma, a następnie ..

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.