Django: Nhóm theo ngày (ngày, tháng, năm)


89

Tôi có một Mô hình đơn giản như thế này:

class Order(models.Model):
    created = model.DateTimeField(auto_now_add=True)
    total = models.IntegerField() # monetary value

Và tôi muốn đưa ra bảng phân tích từng tháng về:

  • Có bao nhiêu lần bán hàng trong một tháng ( COUNT)
  • Giá trị kết hợp ( SUM)

Tôi không chắc cách tốt nhất để tấn công điều này là gì. Tôi đã thấy một số truy vấn chọn thêm trông khá đáng sợ nhưng tâm trí đơn giản của tôi đang nói với tôi rằng tốt hơn hết là tôi chỉ nên lặp lại các con số, bắt đầu từ một năm / tháng bắt đầu tùy ý và đếm ngược cho đến khi tôi đạt đến tháng hiện tại, loại bỏ đơn giản lọc truy vấn cho tháng đó. Nhiều công việc cơ sở dữ liệu hơn - nhà phát triển bớt căng thẳng hơn!

Điều gì có ý nghĩa nhất đối với bạn? Có cách nào hay để tôi có thể lấy lại một bảng dữ liệu nhanh không? Hoặc là phương pháp bẩn thỉu của tôi có lẽ là ý tưởng tốt nhất?

Tôi đang sử dụng Django 1.3. Không chắc liệu họ đã thêm một cách đẹp hơn vào GROUP_BYgần đây hay chưa.


Câu trả lời:


219

Django 1.10 trở lên

Django danh sách tài liệu extranhư bị phản đối ngay . (Cảm ơn bạn đã chỉ ra @seddonym, @ Lucas03). Tôi đã mở một và đây là giải pháp mà jarhwah cung cấp.

from django.db.models.functions import TruncMonth
from django.db.models import Count

Sales.objects
    .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
    .values('month')                          # Group By month
    .annotate(c=Count('id'))                  # Select the count of the grouping
    .values('month', 'c')                     # (might be redundant, haven't tested) select month and count 

Các phiên bản cũ hơn

from django.db import connection
from django.db.models import Sum, Count

truncate_date = connection.ops.date_trunc_sql('month', 'created')
qs = Order.objects.extra({'month':truncate_date})
report = qs.values('month').annotate(Sum('total'), Count('pk')).order_by('month')

Chỉnh sửa

  • Đã thêm số lượng
  • Đã thêm thông tin cho django> = 1.10

1
gì cơ sở dữ liệu backend bạn đang sử dụng - nó hoạt động tốt trong postgres>>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
tback

1
@seddonym cố định (Nhờ jarshwah)
tback

1
Truncmonth không có sẵn trong Django 1,8
Sudhakaran Packianathan

2
cảm ơn, hoạt động tốt. trường hợp góc cho các phiên bản trước 1.10: nếu một tham gia / bộ lọc trên các mô hình khác mà có thể có cùng lĩnh vực (ví dụ: timestamp), sau đó người ta phải hội đủ điều kiện đầy đủ các lĩnh vực -'{}.timestamp'.format(model._meta.db_table)
zsepi

1
Chỉ cần lưu ý nhanh rằng nếu USE_TZcài đặt Django True, hai phiên bản không hoàn toàn tương đương. Phiên bản sử dụng TruncMonthsẽ chuyển đổi dấu thời gian thành múi giờ được chỉ định bởi TIME_ZONEcài đặt trước khi cắt bớt, trong khi phiên bản đang sử dụng date_trunc_sqlsẽ cắt ngắn dấu thời gian UTC thô trong cơ sở dữ liệu.
Daniel Harding

32

Chỉ là một bổ sung nhỏ cho câu trả lời @tback: Nó không hoạt động với tôi với Django 1.10.6 và postgres. Tôi đã thêm order_by () vào cuối để sửa nó.

from django.db.models.functions import TruncMonth
Sales.objects
    .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
    .values('month')                          # Group By month
    .annotate(c=Count('id'))                  # Select the count of the grouping
    .order_by()

1
yup: docs.djangoproject.com/en/1.11/topics/db/aggregation/… ... cảm thấy thiết kế không đẹp nhưng họ rất thông minh những người django, vì vậy nó thực sự là như vậy.
Williams

TruncDatecho phép bạn nhóm theo ngày (ngày trong tháng)
Neil

10

Một cách tiếp cận khác là sử dụng ExtractMonth. Tôi đã gặp sự cố khi sử dụng TruncMonth do chỉ có một giá trị datetime năm được trả về. Ví dụ, chỉ những tháng trong năm 2009 mới được trả về. ExtractMonth đã khắc phục sự cố này một cách hoàn hảo và có thể được sử dụng như bên dưới:

from django.db.models.functions import ExtractMonth
Sales.objects
    .annotate(month=ExtractMonth('timestamp')) 
    .values('month')                          
    .annotate(count=Count('id'))                  
    .values('month', 'count')  

2
    metrics = {
        'sales_sum': Sum('total'),
    }
    queryset = Order.objects.values('created__month')
                               .annotate(**metrics)
                               .order_by('created__month')

Đây querysetlà danh sách Đơn đặt hàng, một dòng mỗi tháng, kết hợp tổng doanh thu:sales_sum

@Django 2.1.7


1

Đây là phương pháp bẩn thỉu của tôi. Nó bẩn.

import datetime, decimal
from django.db.models import Count, Sum
from account.models import Order
d = []

# arbitrary starting dates
year = 2011
month = 12

cyear = datetime.date.today().year
cmonth = datetime.date.today().month

while year <= cyear:
    while (year < cyear and month <= 12) or (year == cyear and month <= cmonth):
        sales = Order.objects.filter(created__year=year, created__month=month).aggregate(Count('total'), Sum('total'))
        d.append({
            'year': year,
            'month': month,
            'sales': sales['total__count'] or 0,
            'value': decimal.Decimal(sales['total__sum'] or 0),
        })
        month += 1
    month = 1
    year += 1

Có thể có một cách tốt hơn để lặp lại năm / tháng nhưng đó không thực sự là điều tôi quan tâm :)


BTW Nó sẽ hoạt động tốt nhưng bạn biết vòng lặp qua nhiều tháng cũng không phải là một ý tưởng tuyệt vời. Điều gì sẽ xảy ra nếu ai đó muốn thực hiện nó trong Ngày của tháng thì vòng lặp này sẽ lặp lại 30-31 ngày. nếu không thì nó hoạt động tốt
Mayank Pratap Singh

điều này là quá chậm nếu bạn có hàng triệu bản ghi
nhau vào

@jierence Hoàn toàn có thể! Tôi đã thêm nó để hiển thị giải pháp của tôi tại thời điểm đăng câu hỏi. Các câu trả lời khác tốt hơn nhiều.
Oli

0

Đây là cách bạn có thể nhóm dữ liệu theo khoảng thời gian tùy ý:

from django.db.models import F, Sum
from django.db.models.functions import Extract, Cast
period_length = 60*15 # 15 minutes

# Annotate each order with a "period"
qs = Order.objects.annotate(
    timestamp=Cast(Extract('date', 'epoch'), models.IntegerField()),
    period=(F('timestamp') / period_length) * period_length,
)

# Group orders by period & calculate sum of totals for each period
qs.values('period').annotate(total=Sum(field))

-1

Theo tháng:

 Order.objects.filter().extra({'month':"Extract(month from created)"}).values_list('month').annotate(Count('id'))

Theo năm:

 Order.objects.filter().extra({'year':"Extract(year from created)"}).values_list('year').annotate(Count('id'))

Theo ngày:

 Order.objects.filter().extra({'day':"Extract(day from created)"}).values_list('day').annotate(Count('id'))

Đừng quên nhập Số lượng

from django.db.models import Count

Đối với django <1.10


3
Vâng, thực hành tuyệt vời, nhập tất cả từ các mô hình
JC Rocamonde

Tôi rõ ràng đã được mỉa mai. Đó là một thực hành kinh khủng để làm điều đó. Bạn không nên làm điều đó và tôi sẽ bỏ phiếu chỉ vì điều đó (mà tôi đã không)
JC Rocamonde
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.