Làm thế nào để truy vấn như NHÓM THEO trong django?


332

Tôi truy vấn một mô hình:

Members.objects.all()

Và nó sẽ trả về:

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

Điều tôi muốn là biết cách Django tốt nhất để thực hiện group_bytruy vấn vào cơ sở dữ liệu của mình, như:

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

Điều đó không làm việc, tất nhiên. Tôi biết chúng ta có thể làm một số thủ thuật trên django/db/models/query.py, nhưng tôi chỉ tò mò muốn biết làm thế nào để làm điều đó mà không cần vá.

Câu trả lời:


483

Nếu bạn muốn tổng hợp, bạn có thể sử dụng các tính năng tổng hợp của ORM :

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

Điều này dẫn đến một truy vấn tương tự như

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

và đầu ra sẽ có dạng

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]

6
@Harry: Bạn có thể chuỗi nó. Một cái gì đó như:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
Eli

57
Tôi có một câu hỏi, truy vấn này chỉ trả về chỉ định và dcount, nếu tôi cũng muốn nhận các giá trị khác của bảng thì sao?
AJ

19
Lưu ý rằng nếu sắp xếp của bạn là một lĩnh vực khác hơn là chỉ định, nó sẽ không làm việc mà không đặt lại các loại. Xem stackoverflow.com/a/1341667/202137
Gidgidonihah

12
@Gidgidonihah Đúng, ví dụ nên đọcMembers.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix

7
Tôi có một câu hỏi, truy vấn này chỉ trả về chỉ định và dcount, nếu tôi cũng muốn nhận các giá trị khác của bảng thì sao?
Yann叶

55

Một giải pháp dễ dàng, nhưng không phải là cách thích hợp là sử dụng SQL liệu :

results = Members.objects.raw('SELECT * FROM myapp_members GROUP BY designation')

Một giải pháp khác là sử dụng group_bytài sản:

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

Bây giờ bạn có thể lặp lại biến kết quả để lấy kết quả của bạn. Lưu ý rằng group_bykhông được ghi lại và có thể được thay đổi trong phiên bản tương lai của Django.

Và ... tại sao bạn muốn sử dụng group_by? Nếu bạn không sử dụng tổng hợp, bạn có thể sử dụng order_byđể đạt được kết quả tương tự.


Bạn có thể vui lòng cho tôi biết làm thế nào để làm điều đó bằng cách sử dụng order_by ??
đơn giản là

2
Xin chào, nếu bạn không sử dụng tổng hợp, bạn có thể mô phỏng group_by bằng cách sử dụng order_by và loại bỏ các mục bạn không cần. Tất nhiên, đây là một mô phỏng và chỉ có thể sử dụng khi sử dụng không nhiều dữ liệu. Vì anh ấy không nói về tập hợp, tôi nghĩ rằng nó có thể là một giải pháp.
Michael

Xin chào, điều này thật tuyệt - bạn có thể giải thích cách sử dụng exec_sql không xuất hiện để hoạt động không ..
rh0dium

8
Lưu ý điều này không còn hoạt động trên Django 1.9. stackoverflow.com/questions/35558120/ từ
grokpot

1
Đây là một cách hack-ish để sử dụng ORM. Bạn không cần phải khởi tạo các truy vấn mới chuyển qua các truy vấn cũ theo cách thủ công.
Ian Kirkpatrick

32

Bạn cũng có thể sử dụng regroupthẻ mẫu để nhóm theo thuộc tính. Từ các tài liệu:

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>

Trông như thế này:

  • Ấn Độ
    • Mumbai: 19.000.000
    • Calcutta: 15.000.000
  • Hoa Kỳ
    • New York: 20.000.000
    • Chicago: 7.000.000
  • Nhật Bản
    • Tokyo: 33.000.000

Nó cũng hoạt động trên QuerySets tôi tin.

nguồn: https://docs.djangoproject.com/en/2.1/ref/temsheet/builtins/#regroup

chỉnh sửa: lưu ý regroupthẻ không hoạt động như bạn mong đợi nếu danh sách từ điển của bạn không được sắp xếp theo khóa. Nó hoạt động lặp đi lặp lại. Vì vậy, sắp xếp danh sách của bạn (hoặc bộ truy vấn) theo khóa của cá mú trước khi chuyển nó vào regroupthẻ.


1
Đây là hoàn hảo! Tôi đã tìm kiếm rất nhiều cách đơn giản để làm điều này. Và nó cũng hoạt động trên các truy vấn, đó là cách tôi sử dụng nó.
CarmenA

1
Điều này là hoàn toàn sai nếu bạn đọc từ cơ sở dữ liệu tập hợp dữ liệu lớn và sau đó chỉ sử dụng các giá trị tổng hợp.
Sławomir Lenart

@ SławomirLenart chắc chắn, điều này có thể không hiệu quả như truy vấn DB thẳng. Nhưng đối với các trường hợp sử dụng đơn giản, nó có thể là một giải pháp tốt
inostia

Điều này sẽ hoạt động nếu kết quả hiển thị trong mẫu. Nhưng, đối với JsonResponse hoặc phản hồi gián tiếp khác. giải pháp này sẽ không hoạt động.
Willy satrio nugroho 20/07/18

1
@Willysatrionugroho nếu bạn muốn làm điều đó trong chế độ xem, ví dụ: stackoverflow.com/questions/477820/ có thể làm việc cho bạn
inostia

7

Bạn cần thực hiện SQL tùy chỉnh như được minh họa trong đoạn trích này:

SQL tùy chỉnh thông qua truy vấn con

Hoặc trong trình quản lý tùy chỉnh như được hiển thị trong tài liệu Django trực tuyến:

Thêm phương thức quản lý bổ sung


1
Loại giải pháp khứ hồi. Tôi đã có thể sử dụng nó, nếu tôi có một số sử dụng mở rộng đó. Nhưng ở đây tôi chỉ cần số lượng thành viên trên mỗi chỉ định đó là tất cả.
đơn giản là

Không vấn đề gì. Tôi đã nghĩ đến việc đề cập đến các tính năng tổng hợp 1.1 nhưng đưa ra giả định rằng bạn đang sử dụng phiên bản phát hành :)
Van Gale

Đó là tất cả về việc sử dụng các truy vấn thô, cho thấy điểm yếu của ORM của Django.
Sławomir Lenart

5

Django không hỗ trợ nhóm miễn phí bằng các truy vấn . Tôi đã học nó theo cách rất xấu. ORM không được thiết kế để hỗ trợ các công cụ như những gì bạn muốn làm mà không sử dụng SQL tùy chỉnh. Bạn bị giới hạn ở:

  • RAW sql (tức là MyModel.objects.raw ())
  • cr.execute câu (và phân tích cú pháp làm bằng tay của kết quả).
  • .annotate() (nhóm theo câu được thực hiện trong mô hình con cho .annotate (), trong các ví dụ như tổng hợp dòng_count = Count ('lines'))).

Qua một bộ truy vấn qsbạn có thể gọi qs.query.group_by = ['field1', 'field2', ...]nhưng sẽ rất rủi ro nếu bạn không biết bạn đang chỉnh sửa truy vấn nào và không đảm bảo rằng nó sẽ hoạt động và không phá vỡ nội bộ của đối tượng Queryset. Bên cạnh đó, đây là API nội bộ (không có giấy tờ) mà bạn không nên truy cập trực tiếp mà không có nguy cơ mã không tương thích với các phiên bản Django trong tương lai.


thực sự bạn bị giới hạn không chỉ trong nhóm miễn phí, vì vậy hãy thử SQLAlchemy thay vì Django ORM.
Sławomir Lenart

5

Có mô-đun cho phép bạn nhóm các mô hình Django và vẫn hoạt động với Truy vấn trong kết quả: https://github.com/kako-nawao/django-group-by

Ví dụ:

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)

'sách / sách.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>

Sự khác biệt đến annotate/ aggregatetruy vấn Django cơ bản là việc sử dụng các thuộc tính của một lĩnh vực có liên quan, ví dụ như book.author.last_name.

Nếu bạn cần PK của các phiên bản đã được nhóm lại với nhau, hãy thêm chú thích sau:

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

LƯU Ý: ArrayAgglà một chức năng cụ thể của Postgres, có sẵn từ Django 1.9 trở đi: https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


Đây django-nhóm-by là một thay thế cho các valuesphương pháp. Đó là cho mục đích khác nhau tôi nghĩ.
LShi

1
@LShi Nó không phải là một thay thế cho các giá trị, tất nhiên là không. valueslà một SQL selecttrong khi group_bylà một SQL group by(như tên chỉ ra ...). Tại sao các downvote? Chúng tôi đang sử dụng mã như vậy trong sản xuất để thực hiện các group_bybáo cáo phức tạp .
Risadinha

Tài liệu của nó nói group_by"hoạt động chủ yếu giống như phương thức giá trị, nhưng với một điểm khác biệt ..." Tài liệu không đề cập đến SQL GROUP BYvà trường hợp sử dụng mà nó cung cấp không cho thấy nó có liên quan gì đến SQL GROUP BY. Tôi sẽ rút lại phiếu bầu xuống khi ai đó đã nói rõ điều này, nhưng tài liệu đó thực sự sai lệch.
LShi

Sau khi đọc tài liệu chovalues , tôi thấy rằng tôi đã bỏ lỡ rằng valueschính nó hoạt động như một NHÓM THEO. Đó là lỗi của tôi. Tôi nghĩ rằng nó đơn giản để sử dụng itertools.groupbyhơn nhóm django này khi valueskhông đủ.
LShi

1
Không thể thực hiện việc group bytừ trên bằng một valuescuộc gọi đơn giản - có hoặc không có annotatevà không lấy mọi thứ từ cơ sở dữ liệu. Đề xuất của bạn về itertools.groupbycông việc cho các bộ dữ liệu nhỏ nhưng không phải cho hàng ngàn bộ dữ liệu mà bạn có thể muốn trang. Tất nhiên, tại thời điểm đó, bạn sẽ phải suy nghĩ về một chỉ mục tìm kiếm đặc biệt có chứa dữ liệu đã được chuẩn bị (đã được nhóm).
Risadinha

0

Các tài liệu nói rằng bạn có thể sử dụng các giá trị vào nhóm queryset.

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

Bạn có thể tìm thấy tất cả các cuốn sách và nhóm chúng theo tên bằng mã này:

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

Bạn có thể xem một số tờ cheet ở đây .


-1

Nếu tôi không nhầm, bạn có thể sử dụng, bất cứ điều gì-query-set .group_by = [' field ']


8
Đây không phải là trường hợp, ít nhất là trong Django 1.6: đối tượng 'Queryset' không có thuộc tính 'group_by'
Facundo Olano

1
Một cách sử dụng phù hợp có thể là queryset.query.group_by = [...] nhưng điều này sẽ phá vỡ ngữ nghĩa của truy vấn và không hoạt động như mong đợi.
Luis Masuelli

-2
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

Đầu tiên bạn cần nhập Sum sau đó ..

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.