Selecione valores distintos de um campo da tabela

104

Estou lutando para entender o ORM do Django. O que eu quero fazer é obter uma lista de valores distintos dentro de um campo da minha tabela .... o equivalente a um dos seguintes:

SELECT DISTINCT myfieldname FROM mytable

(ou alternativamente)

SELECT myfieldname FROM mytable GROUP BY myfieldname

Eu, pelo menos, gostaria de fazer isso do jeito Django antes de recorrer ao sql bruto. Por exemplo, com uma mesa:

id, rua, cidade

1, Main Street, Hull

2, Outra Rua, Casco

3, Bibble Way, Leicester

4, Outra Maneira, Leicester

5, High Street, Londidium

Eu gostaria de obter:

Hull, Leicester, Londidium.

alj
fonte

Respostas:

202

Digamos que seu modelo seja 'Loja'

class Shop(models.Model):
    street = models.CharField(max_length=150)
    city = models.CharField(max_length=150)

    # some of your models may have explicit ordering
    class Meta:
        ordering = ('city')

Uma vez que você pode ter o atributo de Metaclasse orderingdefinido, você pode usar order_by()sem parâmetros para limpar qualquer ordem ao usar distinct(). Veja a documentação em order_by()

Se você não quiser que nenhuma ordem seja aplicada a uma consulta, nem mesmo a ordem padrão, chame order_by () sem parâmetros.

e distinct()na nota onde discute problemas de uso distinct()com pedidos.

Para consultar seu banco de dados, você só precisa chamar:

models.Shop.objects.order_by().values('city').distinct()

Retorna um dicionário

ou

models.Shop.objects.order_by().values_list('city').distinct()

Este retorna um ValuesListQuerySetque você pode converter em a list. Você também pode adicionar flat=Truea values_listpara nivelar os resultados.

Veja também: Obtenha valores distintos de Queryset por campo

Jujule
fonte
29
Na verdade isso funciona. Contudo! Não consegui fazer funcionar em todos os meus modelos. Bem, funcionou em alguns, mas não em outros. Para aqueles que possuem um pedido Meta, ele não funciona. Portanto, você deve primeiro limpar a ordem no queryset. models.Shop.objects.order_by (). values ​​('cidade'). distinto ()
alj
2
É importante notar que values_listna verdade não retorna uma lista. Ele retorna algo como um queryset. Achei útil sempre usar list () em torno de chamadas de values_list.
dheerosaur
8
values_listretorna ValuesListQuerySet, que é um iterador. A conversão para lista pode ser útil, mas também pode afetar o desempenho quando todas as linhas precisam ser avaliadas de uma vez, especialmente com grandes conjuntos de dados.
Peter Kilczuk
3
O Meta: ordering = ()"recurso" de django orm e objects.distinct()vs. objects.ordering().distinct()nos causou horas de confusão. Deve haver um adesivo de advertência de segurança do consumidor nesse produto;) Podemos instituir uma política de atributo de não pedido Meta para evitar arranhões de cabeça no futuro.
horas de
Você pode desligar a Metaclasse orderinge resolver problemas distinctusando order_by()sem parâmetros. Está nos documentos da API QuerySet em order_by()" Se você não quiser que nenhuma ordem seja aplicada a uma consulta, nem mesmo a ordem padrão, chame order_by()sem parâmetros. "
Mark Mikofski
11

Além da resposta ainda muito relevante de jujule , acho muito importante também estar ciente das implicações de order_by()on distinct("field_name")question. Este é, no entanto, um recurso exclusivo do Postgres!

Se você estiver usando o Postgres e definir um nome de campo para o qual a consulta deve ser diferente, será order_by()necessário começar com o mesmo nome de campo (ou nomes de campo) na mesma sequência (pode haver mais campos depois).

Nota

Ao especificar nomes de campo, você deve fornecer um order_by () no QuerySet, e os campos em order_by () devem começar com os campos em badge (), na mesma ordem.

Por exemplo, SELECT DISTINCT ON (a) fornece a primeira linha para cada valor na coluna a. Se você não especificar um pedido, obterá algumas linhas arbitrárias.

Se você quiser, por exemplo, extrair uma lista de cidades nas quais você conhece lojas, o exemplo de jujule teria que ser adaptado para isto:

# returns an iterable Queryset of cities.
models.Shop.objects.order_by('city').values_list('city', flat=True).distinct('city')  
ingofreyer
fonte
2

Por exemplo:

# select distinct code from Platform where id in ( select platform__id from Build where product=p)
pl_ids = Build.objects.values('platform__id').filter(product=p)
platforms = Platform.objects.values_list('code', flat=True).filter(id__in=pl_ids).distinct('code')
platforms = list(platforms) if platforms else []
Roger Sodré
fonte