Seleziona DISTINCT singole colonne in django?


93

Sono curioso di sapere se esiste un modo per eseguire una query in Django che non sia " SELECT * FROM..." sottostante. Sto cercando di fare un " SELECT DISTINCT columnName FROM ..." invece.

Nello specifico ho un modello che assomiglia a:

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

dove Rankè un rango all'interno di a Category. Mi piacerebbe essere in grado di iterare su tutte le categorie facendo qualche operazione su ogni rango all'interno di quella categoria.

Vorrei prima ottenere un elenco di tutte le categorie nel sistema, quindi eseguire una query per tutti i prodotti in quella categoria e ripetere fino a quando ogni categoria non viene elaborata.

Preferisco evitare l'SQL grezzo, ma se devo andare lì, andrebbe bene. Anche se non ho mai codificato SQL grezzo in Django / Python prima.

Risposte:


186

Un modo per ottenere l'elenco di nomi di colonne distinti dal database è utilizzare distinct() insieme a values().

Nel tuo caso puoi fare quanto segue per ottenere i nomi di categorie distinte:

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"

Ci sono un paio di cose da ricordare qui. Innanzitutto, questo restituirà un ValuesQuerySetche si comporta in modo diverso da a QuerySet. Quando accedi ad say, il primo elemento di q(sopra) otterrai un dizionario , NON un'istanza di ProductOrder.

In secondo luogo, sarebbe una buona idea leggere la nota di avvertenza nella documentazione sull'utilizzo didistinct() . L'esempio sopra funzionerà ma tutte le combinazioni di distinct()e values()potrebbero non funzionare.

PS : è una buona idea usare nomi minuscoli per i campi in un modello. Nel tuo caso ciò significherebbe riscrivere il tuo modello come mostrato di seguito:

class ProductOrder(models.Model):
    product  = models.CharField(max_length=20, primary_key=True)
    category = models.CharField(max_length=30)
    rank = models.IntegerField()

1
Il metodo descritto di seguito è ora disponibile in django 1.4 ed è utile se hai bisogno di un'istanza ProductOrder con distinto riconoscimento del campo ;-)
Jonathan Liuti

62

In realtà è abbastanza semplice se stai usando PostgreSQL , usa semplicemente distinct(columns)( documentazione ).

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

Nota che questa funzione è stata inclusa in Django dalla 1.4


@lazerscience, @Manoj Govindan: mi dispiace, hai ragione. Sembra che io abbia patchato Django per aggiungere quella funzionalità. Ho aggiunto un collegamento alla patch
Wolph il

3
Questo è ora in Django SVN e sarà in Django 1.4
Will Hardy

14
Nota: a meno che tu non stia usando PostgreSQL, non puoi dare un argomento a separate (). È meglio attenersi alla soluzione accettata sopra.
Mark Chackerian

nei test, questo can_distinct_on_fieldssembra essere solo per Postgres
Skylar Saveland,

3
più 1, ma all()non è necessario qui
Antony Hatchkins

17

Le altre risposte vanno bene, ma questo è un po 'più pulito, in quanto fornisce solo i valori come si otterrebbero da una query DISTINCT, senza alcun cruft da Django.

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

o

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

E funziona senza PostgreSQL.

Questo è meno efficiente rispetto all'utilizzo di un .distinct (), presumendo che DISTINCT nel tuo database sia più veloce di un Python set, ma è ottimo per spaghettare la shell.


values_listnon inserisce DISTINCTla query sql, quindi questo porterebbe più valori se ci fossero.
mehmet

13

L'utente ordina per con quel campo e poi fa distinzione.

ProductOrder.objects.order_by('category').values_list('category', flat=True).distinct()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.