web-dev-qa-db-fra.com

Comment interroger en tant que GROUP BY dans Django?

J'interroge un modèle, 

Members.objects.all()

et il retourne dire

Eric, Salesman, X-Shop
Freddie, Manager, X2-Shop
Teddy, Salesman, X2-Shop
Sean, Manager, X2-Shop

Ce que je veux, c’est de connaître le meilleur moyen pour Django d’envoyer une requête group_by à ma base de données, comme,

Members.objects.all().group_by('designation')

Ce qui ne marche pas, bien sûr . Je sais que nous pouvons faire quelques astuces sur "Django/db/models/query.py", Mais je suis juste curieux de savoir comment le faire sans mettre à jour.

256
simplyharsh

Si vous voulez faire de l'agrégation, vous pouvez utiliser les fonctionnalités d'agrégation de l'ORM :

from Django.db.models import Count
Members.objects.values('designation').annotate(dcount=Count('designation'))

Il en résulte une requête similaire à

SELECT designation, COUNT(designation) AS dcount
FROM members GROUP BY designation

et la sortie serait de la forme

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]
394
Guðmundur H

Une solution simple, mais pas appropriée, consiste à utiliser RAW-SQL:

http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql

Une autre solution consiste à utiliser la propriété group_by:

query = Members.objects.all().query
query.group_by = ['designation']
results = QuerySet(query=query, model=Members)

Vous pouvez maintenant parcourir la variable de résultats pour récupérer vos résultats. Notez que group_by n'est pas documenté et pourrait être modifié dans les futures versions de Django.

Et ... pourquoi voulez-vous utiliser group_by? Si vous n'utilisez pas l'agrégation, vous pouvez utiliser order_by pour obtenir un résultat identique.

40
Michael

Vous pouvez également utiliser la balise de modèle regroup pour regrouper par attributs. De la docs:

cities = [
    {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'},
    {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'},
    {'name': 'New York', 'population': '20,000,000', 'country': 'USA'},
    {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'},
    {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'},
]

...

{% regroup cities by country as country_list %}

<ul>
    {% for country in country_list %}
        <li>{{ country.grouper }}
            <ul>
            {% for city in country.list %}
                <li>{{ city.name }}: {{ city.population }}</li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

Ressemble à ça:

  • Inde
    • Mumbai: 19 000 000
    • Calcutta: 15 000 000 
  • ETATS-UNIS
    • New York: 20 000 000
    • Chicago: 7 000 000
  • Japon
    • Tokyo: 33 000 000

Cela fonctionne également sur QuerySets je crois.

source: https://docs.djangoproject.com/fr/1.11/ref/templates/builtins/#regroup

10
inostia

Vous devez créer du code SQL personnalisé, comme illustré dans cet extrait:

SQL personnalisé via une sous-requête

Ou dans un gestionnaire personnalisé, comme indiqué dans les documents Django en ligne:

Ajout de méthodes Manager supplémentaires

5
Van Gale

Il existe un module qui vous permet de regrouper des modèles Django et de continuer à utiliser un QuerySet dans le résultat: https://github.com/kako-nawao/Django-group-by

Par exemple:

from Django_group_by import GroupByMixin

class BookQuerySet(QuerySet, GroupByMixin):
    pass

class Book(Model):
    title = TextField(...)
    author = ForeignKey(User, ...)
    shop = ForeignKey(Shop, ...)
    price = DecimalField(...)

class GroupedBookListView(PaginationMixin, ListView):
    template_name = 'book/books.html'
    model = Book
    paginate_by = 100

    def get_queryset(self):
        return Book.objects.group_by('title', 'author').annotate(
            shop_count=Count('shop'), price_avg=Avg('price')).order_by(
            'name', 'author').distinct()

    def get_context_data(self, **kwargs):
        return super().get_context_data(total_count=self.get_queryset().count(), **kwargs)

'book/books.html'

<ul>
{% for book in object_list %}
    <li>
        <h2>{{ book.title }}</td>
        <p>{{ book.author.last_name }}, {{ book.author.first_name }}</p>
        <p>{{ book.shop_count }}</p>
        <p>{{ book.price_avg }}</p>
    </li>
{% endfor %}
</ul>

La différence par rapport aux requêtes Django de base annotate/aggregate de base réside dans l'utilisation des attributs d'un champ associé, par exemple. book.author.last_name.

Si vous avez besoin des PC des instances qui ont été regroupées, ajoutez l'annotation suivante:

.annotate(pks=ArrayAgg('id'))

NOTE: ArrayAgg est une fonction spécifique à Postgres, disponible à partir de Django 1.9: https://docs.djangoproject.com/fr/1.10/ref/contrib/postgres/aggregates/#arrayagg

4
Risadinha

Django ne supporte pas le groupe libre par requêtes . Je l'ai appris de très mauvaise manière. ORM n'est pas conçu pour prendre en charge des tâches telles que ce que vous voulez faire, sans utiliser de code SQL personnalisé. Vous êtes limité à:

  • RAW sql (c'est-à-dire MyModel.objects.raw ())
  • cr.execute phrases (et une analyse manuelle du résultat).
  • .annotate() (le groupe par phrases est effectué dans le modèle enfant pour .annotate (), dans des exemples tels que l'agrégation de lines_count = Count ('lines')).

Sur un ensemble de requêtes qs, vous pouvez appeler qs.query.group_by = ['field1', 'field2', ...], mais cela est risqué si vous ne savez pas quelle requête vous modifiez et si vous n'avez aucune garantie que cela fonctionnera et si vous ne cassez pas les éléments internes de l'objet QuerySet. En outre, il s’agit d’une API interne (non documentée) à laquelle vous ne devriez pas accéder directement sans risquer que le code ne soit plus compatible avec les futures versions de Django.

3
Luis Masuelli

Le document indique que vous pouvez utiliser des valeurs pour regrouper le jeu de requêtes. 

class Travel(models.Model):
    interest = models.ForeignKey(Interest)
    user = models.ForeignKey(User)
    time = models.DateTimeField(auto_now_add=True)

# Find the travel and group by the interest:

>>> Travel.objects.values('interest').annotate(Count('user'))
<QuerySet [{'interest': 5, 'user__count': 2}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited for 2 times, 
# and the interest(id=6) had only been visited for 1 time.

>>> Travel.objects.values('interest').annotate(Count('user', distinct=True)) 
<QuerySet [{'interest': 5, 'user__count': 1}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited by only one person (but this person had 
#  visited the interest for 2 times

Vous pouvez trouver tous les livres et les regrouper par nom en utilisant ce code:

Book.objects.values('name').annotate(Count('id')).order_by() # ensure you add the order_by()

Vous pouvez regarder des feuilles de cheet ici .

0
ramwin