Как сделать запрос как GROUP BY в Django?

222

Я запрашиваю модель,

Members.objects.all()

и он возвращает:

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

Я хочу, чтобы узнать лучший способ Django запрос group_by к моему db, как будто,

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

Это не работает, конечно. Я знаю, что мы можем сделать некоторые трюки на "django/db/models/query.py", но мне просто интересно узнать, как это сделать без исправления.

  • 2
    Я был бы довольно забавный синтаксис все же.
Теги:

8 ответов

358
Лучший ответ

Если вы хотите сделать агрегацию, вы можете использовать функции агрегации ORM:

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

В результате возникает запрос, похожий на

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

и выход будет иметь вид

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]
  • 0
    Вы не поверите, что я жонглировал одним и тем же фрагментом кода в настоящее время. Да, 1.1 есть довольно хорошие вещи, чтобы смотреть.
  • 3
    Как бы вы добавили еще один фильтр, чтобы, скажем, искать разные значения по дате?
Показать ещё 11 комментариев
36

Простое решение, но не правильно, это использовать RAW-SQL:

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

Другим решением является использование свойства group_by:

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

Теперь вы можете перебирать переменную результатов для получения результатов. Обратите внимание, что group_by не документируется и может быть изменен в будущей версии Django.

И... почему вы хотите использовать group_by? Если вы не используете агрегацию, вы можете использовать order_by для достижения аналогичного результата.

  • 0
    Подскажите пожалуйста, как это сделать с помощью order_by ??
  • 2
    Привет, если вы не используете агрегацию, вы можете эмулировать group_by с помощью order_by и удалить ненужные записи. Конечно, это эмуляция, и ее можно использовать только при небольшом количестве данных. Поскольку он не говорил об агрегации, я подумал, что это может быть решением.
Показать ещё 4 комментария
5

Вам нужно сделать пользовательский SQL, как показано в этом фрагменте:

Пользовательский SQL через подзапрос

Или в пользовательском менеджере, как показано в онлайн-документах Django:

Добавление дополнительных методов Менеджера

  • 1
    Вид двустороннего решения. Я бы использовал это, если бы у меня было некоторое расширенное использование этого. Но здесь мне просто нужно количество членов на обозначение, вот и все.
  • 0
    Нет проблем. Я подумал упомянуть о возможностях агрегации 1.1, но сделал предположение, что вы используете версию выпуска :)
Показать ещё 1 комментарий
4

Вы также можете использовать тег шаблона regroup для группировки по атрибутам. Из документов:

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>

Похож:

  • Индия
    • Мумбаи: 19 000 000
    • Калькутта: 15 000 000
  • США
    • Нью-Йорк: 20 000 000
    • Чикаго: 7 000 000
  • Япония
    • Токио: 33 000 000

Он также работает на QuerySet Я верю.

источник: https://docs.djangoproject.com/en/1.11/ref/templates/builtins/#regroup

  • 1
    Это потрясающе! Я много искал простой способ сделать это. И это также работает с наборами запросов, вот как я это использовал.
  • 1
    Это совершенно неправильно, если вы читаете из базы данных большой набор данных, а затем просто используете агрегированные значения.
Показать ещё 3 комментария
3

Существует модуль, который позволяет группировать модели Django и по-прежнему работать с QuerySet в результате: https://github.com/kako-nawao/django-group-by

Например:

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)

'книга/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>

Разница с базовыми Django-запросами annotate/aggregate заключается в использовании атрибутов связанного поля, например. book.author.last_name.

Если вам нужны PK экземпляров, которые были сгруппированы вместе, добавьте следующую аннотацию:

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

ПРИМЕЧАНИЕ. ArrayAgg - это специальная функция Postgres, доступная с Django 1.9 и далее: https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg

  • 0
    Этот django-group-by является альтернативой методу values . Это для другой цели, я думаю.
  • 1
    @LShi Это, конечно, не альтернатива ценностям. values - это select SQL, а group_by - это group by SQL (как следует из названия ...). Почему отрицательный голос? Мы используем такой код в производстве для реализации сложных операторов group_by .
Показать ещё 5 комментариев
3

Django не поддерживает свободную группу по запросам. Я узнал это очень плохо. ORM не предназначен для поддержки таких вещей, как то, что вы хотите сделать, без использования пользовательского SQL. Вы ограничены:

  • RAW sql (т.е. MyModel.objects.raw())
  • cr.execute (и ручной анализ результата).
  • .annotate() (группа по предложениям выполняется в дочерней модели для .annotate() в примерах, таких как aggregating lines_count = Count ('lines'))).

В запросе qs вы можете вызвать qs.query.group_by = ['field1', 'field2', ...], но это рискованно, если вы не знаете, какой запрос вы редактируете, и не имеете гарантии, что он будет работать, а не нарушать внутренние объекты QuerySet. Кроме того, это внутренний (недокументированный) API, к которому вы не должны обращаться напрямую, не рискуя тем, что код больше не совместим с будущими версиями Django.

  • 0
    на самом деле вы ограничены не только в свободном групповом доступе, поэтому попробуйте SQLAlchemy вместо Django ORM.
0

В документе говорится, что вы можете использовать значения для группировки набора запросов.

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

Вы можете найти все книги и сгруппировать их по имени с помощью этого кода:

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

Вы можете посмотреть некоторые листовки здесь.

-2

Если я не ошибаюсь, вы можете использовать, независимо от запроса-set.group_by = ['field']

  • 7
    Это не так, по крайней мере, в Django 1.6: у объекта 'QuerySet' нет атрибута 'group_by'
  • 0
    Правильное использование может быть queryset.query.group_by = [...], но это нарушит семантику запроса и будет работать не так, как ожидалось.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню