¿Seleccionar columnas individuales DISTINCT en django?

93

Tengo curiosidad por saber si hay alguna forma de hacer una consulta en Django que no sea " SELECT * FROM..." debajo. Estoy tratando de hacer un " SELECT DISTINCT columnName FROM ..." en su lugar.

Específicamente tengo un modelo que se parece a:

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

donde Rankes un rango dentro de a Category. Me gustaría poder iterar sobre todas las categorías haciendo alguna operación en cada rango dentro de esa categoría.

Primero me gustaría obtener una lista de todas las categorías en el sistema y luego consultar todos los productos en esa categoría y repetir hasta que se procese cada categoría.

Prefiero evitar SQL sin formato, pero si tengo que ir allí, estaría bien. Aunque nunca antes había codificado SQL sin formato en Django / Python.

Jamida
fuente

Respuestas:

186

Una forma de obtener la lista de nombres de columnas distintos de la base de datos es usar distinct() junto con values().

En su caso, puede hacer lo siguiente para obtener los nombres de categorías distintas:

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"

Hay un par de cosas para recordar aquí. Primero, esto devolverá a ValuesQuerySetque se comporta de manera diferente a a QuerySet. Cuando acceda, digamos, el primer elemento de q(arriba) obtendrá un diccionario , NO una instancia de ProductOrder.

En segundo lugar, sería una buena idea leer la nota de advertencia en los documentos sobre el uso de distinct(). El ejemplo anterior funcionará, pero todas las combinaciones de distinct()y values()puede que no.

PD : es una buena idea usar nombres en minúsculas para los campos en un modelo. En su caso, esto significaría reescribir su modelo como se muestra a continuación:

class ProductOrder(models.Model):
    product  = models.CharField(max_length=20, primary_key=True)
    category = models.CharField(max_length=30)
    rank = models.IntegerField()
Manoj Govindan
fuente
1
El método que se describe a continuación ahora está disponible en django 1.4 y es bueno si necesita una instancia de ProductOrder con reconocimiento de campo distinto ;-)
Jonathan Liuti
62

En realidad, es bastante simple si está usando PostgreSQL , solo use distinct(columns)( documentación ).

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

Tenga en cuenta que esta función se ha incluido en Django desde 1.4

Wolph
fuente
@lazerscience, @Manoj Govindan: Lo siento, tienes razón. Parece que he parcheado Django para agregar esa característica.
Agregué
3
Esto ahora está en Django SVN y estará en Django 1.4
Will Hardy
14
Nota: a menos que esté usando PostgreSQL, no puede dar un argumento distinto (). Lo mejor es seguir con la solución aceptada anteriormente.
Mark Chackerian
en las pruebas, esto es lo can_distinct_on_fieldsque parece ser solo de Postgres
Skylar Saveland
3
más 1, pero all()no es necesario aquí
Antony Hatchkins
17

Las otras respuestas están bien, pero esto es un poco más limpio, ya que solo proporciona los valores que obtendría de una consulta DISTINCT, sin ningún cruft de 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']

Y funciona sin PostgreSQL.

Esto es menos eficiente que usar un .distinct (), asumiendo que DISTINCT en su base de datos es más rápido que un python set, pero es genial para moverse por el shell.

Mark Chackerian
fuente
values_listno coloca DISTINCTen la consulta sql, por lo que esto traería múltiples valores si los hubiera.
mehmet
13

El usuario ordena con ese campo y luego lo hace distinto.

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