Sélectionnez DISTINCT les colonnes individuelles dans django?

93

Je suis curieux de savoir s'il existe un moyen de faire une requête dans Django qui ne soit pas un " SELECT * FROM..." en dessous. J'essaye de faire un " SELECT DISTINCT columnName FROM ..." à la place.

Plus précisément, j'ai un modèle qui ressemble à:

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

où le Rankest un rang dans a Category. J'aimerais pouvoir parcourir toutes les catégories en effectuant des opérations sur chaque rang de cette catégorie.

J'aimerais d'abord obtenir une liste de toutes les catégories du système, puis interroger tous les produits de cette catégorie et répéter jusqu'à ce que chaque catégorie soit traitée.

Je préfère éviter le SQL brut, mais si je dois y aller, ce serait bien. Bien que je n'ai jamais codé SQL brut dans Django / Python auparavant.

Jamida
la source

Réponses:

185

Une façon d'obtenir la liste des noms de colonnes distincts à partir de la base de données consiste à utiliser distinct() conjointement avec values().

Dans votre cas, vous pouvez faire ce qui suit pour obtenir les noms de catégories distinctes:

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"

Il y a quelques choses à retenir ici. Tout d'abord, cela renverra a ValuesQuerySetqui se comporte différemment de a QuerySet. Lorsque vous accédez, disons, au premier élément de q(ci-dessus), vous obtiendrez un dictionnaire , PAS une instance de ProductOrder.

Deuxièmement, ce serait une bonne idée de lire la note d'avertissement dans la documentation sur l'utilisation distinct(). L'exemple ci-dessus fonctionnera, mais toutes les combinaisons de distinct()et values()peuvent ne pas l'être.

PS : c'est une bonne idée d'utiliser des noms en minuscules pour les champs d'un modèle. Dans votre cas, cela signifierait réécrire votre modèle comme indiqué ci-dessous:

class ProductOrder(models.Model):
    product  = models.CharField(max_length=20, primary_key=True)
    category = models.CharField(max_length=30)
    rank = models.IntegerField()
Manoj Govindan
la source
1
La méthode décrite ci-dessous est maintenant disponible dans django 1.4 et est bien si vous avez besoin d'une instance ProductOrder avec une prise en compte des champs distincte ;-)
Jonathan Liuti
61

C'est assez simple en fait si vous utilisez PostgreSQL , utilisez simplement distinct(columns)( documentation ).

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

Notez que cette fonctionnalité est incluse dans Django depuis la 1.4

Wolph
la source
@lazerscience, @Manoj Govindan: Je suis désolé, vous avez raison. Il semble que j'ai patché Django pour ajouter cette fonctionnalité. J'ai ajouté un lien vers le patch
Wolph
3
Ceci est maintenant dans Django SVN et sera dans Django 1.4
Will Hardy
14
Remarque: sauf si vous utilisez PostgreSQL, vous ne pouvez pas donner d'argument à distinct (). Meilleur bâton avec la solution acceptée ci-dessus.
Mark Chackerian
dans les tests, c'est ce can_distinct_on_fieldsqui semble être uniquement
PostgreSQL
3
plus 1, mais all()n'est pas nécessaire ici
Antony Hatchkins
17

Les autres réponses sont correctes, mais c'est un peu plus propre, en ce sens que cela ne donne que les valeurs comme vous obtiendriez d'une requête DISTINCT, sans aucune cruauté de Django.

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

ou

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

Et cela fonctionne sans PostgreSQL.

C'est moins efficace que d'utiliser un .distinct (), en supposant que DISTINCT dans votre base de données est plus rapide qu'un python set, mais c'est génial pour nouiller autour du shell.

Mark Chackerian
la source
values_listne met pas DISTINCTdans la requête SQL, donc cela apporterait plusieurs valeurs s'il y en avait.
mehmet le
13

L'utilisateur commande par avec ce champ, puis fait distinct.

ProductOrder.objects.order_by('category').values_list('category', flat=True).distinct()
SuperNova
la source