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.
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}]
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.
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:
Cela fonctionne également sur QuerySet
s je crois.
source: https://docs.djangoproject.com/fr/1.11/ref/templates/builtins/#regroup
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:
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
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é à:
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.
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 .