14

Buduję bazę danych rejestrowania żywności w Django i mam problem związany z zapytaniem.Jak sortować według adnotacji Count() w pokrewnym modelu w Django

Skonfigurowałem moje modele, aby uwzględnić (między innymi) model Food podłączony do modelu użytkownika poprzez pole "konsumenta" M2M za pośrednictwem modelu konsumpcji. Model żywności opisuje potrawy z potraw, a model konsumpcji opisuje spożycie żywności przez użytkownika (data, ilość itp.).

class Food(models.Model): 
    food_name = models.CharField(max_length=30) 
    consumer = models.ManyToManyField("User", through=Consumption) 

class Consumption(models.Model): 
    food = models.ForeignKey("Food") 
    user = models.ForeignKey("User") 

Chcę utworzyć kwerendę, która zwraca wszystkie obiekty żywności zamówionej przez liczbę razy, że pojawia się obiekt żywności w tabeli zużyciu przez tego użytkownika (ile razy użytkownik spożywanego jedzenia).

Próbuję coś w linii:

Food.objects.all().annotate(consumption_times = Count(consumer)).order_by('consumption_times')` 

ale to oczywiście liczyć wszystkie obiekty wydatkowe związane z przedmiotem jedzenie, nie tylko te związane z użytkownikiem. Czy muszę zmieniać modele lub czy brakuje mi czegoś oczywistego w zapytaniach?

Jest to dość czasochłonna operacja (między innymi służy do wypełniania pola autouzupełniania w interfejsie użytkownika), a tabela zawiera kilka tysięcy wpisów, więc wolę sortowanie w bazie danych koniec, zamiast robić metodę brute force i iteracyjne nad wynikami robi:

Consumption.objects.filter(food=food, user=user).count() 

a następnie przy użyciu Pythona sortowania je posortować. Nie sądzę, że ta metoda skalowałaby się bardzo dobrze, ponieważ baza użytkowników wzrasta, a ja chcę zaprojektować bazę danych jako dowód przyszłości, jak tylko mogę od samego początku.

Wszelkie pomysły?

+0

Możliwy duplikat [Zamówienia hrabiego polu ForeignKey?] (Http://stackoverflow.com/questions/2501149/order-by-count-of-a-foreignkey-field) –

Odpowiedz

21

Być może coś takiego?

Food.objects.filter(consumer__user=user)\ 
      .annotate(consumption_times=Count('consumer'))\ 
      .order_by('consumption_times') 
+0

ale to tylko zwrócić przedmioty żywnościowe, które zostały zużyte w pewnym momencie, prawda? Chcę zwrócić wszystkie obiekty Żywności, ale w kolejności najczęściej spożywanych jako pierwsze. Jeśli filtruję według użytkownika, nie otrzymam żywności, która nie została jeszcze skonsumowana. Jednym z pomysłów może być wykonanie dwóch zapytań, pierwszy taki, jaki zaproponowałeś, aby wszystkie produkty spożywcze zostały skonsumowane co najmniej raz, a następnie coś podobnego do linii Food.objects.exclude (consumer__user = user) i wypełnij listę tymi . Czy to działa? –

+0

Tak, 2 pytania byłyby takie, jak bym to zrobił. – SmileyChris

19

Mam bardzo podobny problem. Zasadniczo, wiem, że zapytanie SQL chcesz to:

SELECT food.*, COUNT(IF(consumption.user_id=123,TRUE,NULL)) AS consumption_times 
     FROM food LEFT JOIN consumption ON (food.id=consumption.food_id) 
     ORDER BY consumption_times; 

Co Żałuję, że można mieszać zagregowanych funkcji i ekspresji F, opisywanie wyrażenia F bez funkcji zbiorczej, mają bogatszy zestaw operacji/funkcji do Wyrażenia F i mają wirtualne pola, które są w zasadzie automatyczną adnotacją wyrażeń F. Tak, że można zrobić:

Food.objects.annotate(consumption_times=Count(If(F('consumer')==user,True,None)))\ 
      .order_by('consumtion_times') 

Ponadto, po prostu będąc w stanie łatwiej mogli dodawać własne złożone funkcje agregujące byłoby miło, ale w międzyczasie, oto hack który dodaje łączną funkcję, aby to zrobić.

from django.db.models import aggregates,sql 
class CountIf(sql.aggregates.Count): 
    sql_template = '%(function)s(IF(%(field)s=%(equals)s,TRUE,NULL))' 
sql.aggregates.CountIf = CountIf 

consumption_times = aggregates.Count('consumer',equals=user.id) 
consumption_times.name = 'CountIf' 
rows = Food.objects.annotate(consumption_times=consumption_times)\ 
        .order_by('consumption_times') 
+0

To jest niesamowite !!! Dzięki człowieku, uratowałeś mój dzień!Postaram się, aby wyglądało to nieco ładniej, ale zdecydowanie powinieneś umieścić to w ścieżce django. –