Wybrać DISTINCT poszczególne kolumny w django?

96

Jestem ciekawy, czy istnieje sposób na wykonanie zapytania w Django, które nie jest „ SELECT * FROM...” poniżej. SELECT DISTINCT columnName FROM ...Zamiast tego próbuję wykonać „ ”.

Konkretnie mam model, który wygląda następująco:

class ProductOrder(models.Model):
   Product  = models.CharField(max_length=20, promary_key=True)
   Category = models.CharField(max_length=30)
   Rank = models.IntegerField()

gdzie Rankjest rangą w ramach Category. Chciałbym móc iterować po wszystkich kategoriach, wykonując pewne operacje na każdej pozycji w tej kategorii.

Chciałbym najpierw uzyskać listę wszystkich kategorii w systemie, a następnie zapytać o wszystkie produkty w tej kategorii i powtarzać, aż każda kategoria zostanie przetworzona.

Wolałbym unikać surowego SQL, ale jeśli będę musiał tam iść, to będzie w porządku. Chociaż nigdy wcześniej nie kodowałem surowego SQL w Django / Pythonie.

jamida
źródło

Odpowiedzi:

194

Jednym ze sposobów uzyskania listy różnych nazw kolumn z bazy danych jest użycie distinct() w połączeniu z values().

W swoim przypadku możesz wykonać następujące czynności, aby uzyskać nazwy różnych kategorii:

q = ProductOrder.objects.values('Category').distinct()
print q.query # See for yourself.

# The query would look something like
# SELECT DISTINCT "app_productorder"."category" FROM "app_productorder"

Należy pamiętać o kilku rzeczach. Po pierwsze, zwróci to a, ValuesQuerySetktóry zachowuje się inaczej niż a QuerySet. Kiedy uzyskasz dostęp, powiedzmy, pierwszy element q(powyżej) otrzymasz słownik , a NIE instancję ProductOrder.

Po drugie, dobrym pomysłem byłoby przeczytanie ostrzeżenia w dokumentacji dotyczącej używania distinct(). Powyższy przykład zadziała, ale wszystkie kombinacje distinct()i values()mogą nie.

PS : dobrym pomysłem jest używanie nazw pól w modelu małymi literami . W twoim przypadku oznaczałoby to przepisanie modelu, jak pokazano poniżej:

class ProductOrder(models.Model):
    product  = models.CharField(max_length=20, primary_key=True)
    category = models.CharField(max_length=30)
    rank = models.IntegerField()
Manoj Govindan
źródło
1
Opisana poniżej metoda jest teraz dostępna w django 1.4 i jest fajna, jeśli potrzebujesz instancji ProductOrder z odrębnym uwzględniającym pola ;-)
Jonathan Liuti
64

To całkiem proste, jeśli używasz PostgreSQL , po prostu użyj distinct(columns)( dokumentacja ).

Productorder.objects.all().distinct('category')

Zauważ, że ta funkcja została zawarta w Django od 1.4

Wolph
źródło
@lazerscience, @Manoj Govindan: Przepraszam, masz rację. Wygląda na to, że załatałem Django, aby dodać tę funkcję. Dodałem link do łatki
Wolph
3
To jest teraz w Django SVN i będzie w Django 1.4
Will Hardy
14
Uwaga: jeśli nie używasz PostgreSQL, nie możesz podać argumentu wyraźnej (). Najlepiej trzymać się przyjętego rozwiązania powyżej.
Mark Chackerian
w testach to can_distinct_on_fieldswydaje się być tylko Postgresem
Skylar Saveland
4
plus 1, ale all()nie jest tutaj konieczne
Antony Hatchkins
17

Pozostałe odpowiedzi są w porządku, ale jest to trochę czystsze, ponieważ podaje tylko wartości takie, jakie można uzyskać z zapytania DISTINCT, bez żadnego cruft z Django.

>>> set(ProductOrder.objects.values_list('category', flat=True))
{u'category1', u'category2', u'category3', u'category4'}

lub

>>> list(set(ProductOrder.objects.values_list('category', flat=True)))
[u'category1', u'category2', u'category3', u'category4']

I działa bez PostgreSQL.

Jest to mniej wydajne niż użycie .distinct (), zakładając, że DISTINCT w twojej bazie danych jest szybszy niż python set, ale świetnie nadaje się do makaronu wokół powłoki.

Mark Chackerian
źródło
values_listnie umieszcza DISTINCTw zapytaniu sql, więc przyniosłoby to wiele wartości, gdyby istniały.
mehmet
13

Sortuj użytkowników według tego pola, a następnie rozróżnij.

ProductOrder.objects.order_by('category').values_list('category', flat=True).distinct()
SuperNova
źródło