Django: จัดกลุ่มตามวันที่ (วันเดือนปี)


94

ฉันมีโมเดลง่ายๆดังนี้:

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

และฉันต้องการแสดงการแยกย่อยแบบเดือนต่อเดือนของ:

  • ในหนึ่งเดือนมียอดขายเท่าใด ( COUNT)
  • มูลค่ารวม ( SUM)

ฉันไม่แน่ใจว่าวิธีที่ดีที่สุดในการโจมตีนี้คืออะไร ฉันเคยเห็นข้อความค้นหาพิเศษที่ดูค่อนข้างน่ากลัว แต่ความคิดง่ายๆของฉันกำลังบอกฉันว่าฉันอาจจะดีกว่าแค่การวนซ้ำตัวเลขโดยเริ่มจากปี / เดือนเริ่มต้นโดยพลการและนับไปเรื่อย ๆ จนกว่าฉันจะถึงเดือนปัจจุบันทิ้งง่ายๆ การกรองคำค้นหาสำหรับเดือนนั้น งานฐานข้อมูลมากขึ้น - ความเครียดของนักพัฒนาน้อยลง!

อะไรที่เหมาะสมกับคุณที่สุด มีวิธีที่ดีที่ฉันสามารถดึงตารางข้อมูลด่วนกลับมาได้หรือไม่? หรือวิธีสกปรกของฉันอาจเป็นความคิดที่ดีที่สุด?

ฉันใช้ Django 1.3 ไม่แน่ใจว่าพวกเขาได้เพิ่มวิธีที่ดีกว่าGROUP_BYเมื่อเร็ว ๆ นี้หรือไม่


คำตอบ:


225

Django 1.10 ขึ้นไป

Django รายการเอกสารextraเป็นเลิกเร็ว ๆ นี้ (ขอบคุณที่ชี้ว่า @seddonym, @ Lucas03) ฉันเปิดตั๋วและนี่คือทางออกที่ jarshwah ให้ไว้

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 

เวอร์ชันเก่ากว่า

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')

การแก้ไข

  • เพิ่มจำนวน
  • เพิ่มข้อมูลสำหรับ django> = 1.10

1
คุณใช้แบ็กเอนด์ฐานข้อมูลใด - ใช้งานได้ดีใน postgres>>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
ย้อนกลับ

1
@seddonym คงที่ (ขอบคุณ jarshwah)
กลับ

1
Truncmonth ไม่มีใน Django 1.8
Sudhakaran Packianathan

2
ขอบคุณทำงานได้ดี กรณีเข้ามุมสำหรับรุ่นก่อน 1.10: หากมีการรวม / กรองในรุ่นอื่นซึ่งอาจมีฟิลด์เดียวกัน (เช่นการประทับเวลา) หนึ่งจะต้องมีคุณสมบัติครบถ้วนของฟิลด์ -'{}.timestamp'.format(model._meta.db_table)
zsepi

1
โปรดทราบโดยย่อว่าหากการUSE_TZตั้งค่าDjango เป็นTrueทั้งสองเวอร์ชันจะไม่เท่ากัน เวอร์ชันที่ใช้TruncMonthจะแปลงการประทับเวลาเป็นเขตเวลาที่ระบุโดยการTIME_ZONEตั้งค่าก่อนที่จะตัดทอนในขณะที่เวอร์ชันที่ใช้date_trunc_sqlจะตัดการประทับเวลา UTC แบบดิบในฐานข้อมูล
Daniel Harding

36

เพิ่มคำตอบ @tback เพียงเล็กน้อย: มันไม่ได้ผลสำหรับฉันกับ Django 1.10.6 และ postgres ฉันเพิ่ม order_by () ในตอนท้ายเพื่อแก้ไข

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/th/1.11/topics/db/aggregation/… ... ไม่รู้สึกว่ามีดีไซน์ที่ดี แต่พวกเขาฉลาดมากพวก django พวกนั้นก็เลยเป็นจริง
Williams

TruncDateให้คุณจัดกลุ่มตามวันที่ (วันในเดือน)
นีล

11

ExtractMonthอีกวิธีหนึ่งคือการใช้งาน ฉันประสบปัญหาในการใช้ TruncMonth เนื่องจากมีการส่งคืนค่าปีวันที่และเวลาเพียงค่าเดียว ตัวอย่างเช่นเฉพาะเดือนในปี 2009 ที่ถูกส่งกลับ ExtractMonth แก้ไขปัญหานี้ได้อย่างสมบูรณ์แบบและสามารถใช้งานได้ดังนี้:

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')

querysetคือรายการของคำสั่งหนึ่งบรรทัดต่อเดือนรวมผลรวมของยอดขาย:sales_sum

@ ดีจังโก 2.1.7


1

นี่คือวิธีสกปรกของฉัน มันสกปรก.

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

อาจมีวิธีที่ดีกว่าในการวนรอบปี / เดือน แต่นั่นไม่ใช่สิ่งที่ฉันสนใจจริงๆ :)


BTW มันจะทำงานได้ดี แต่คุณรู้ว่าการวนซ้ำหลายเดือนไม่ใช่ความคิดที่ดี จะเกิดอะไรขึ้นถ้ามีคนต้องการทำในวันของเดือนลูปนี้จะวนซ้ำภายใน 30-31 วัน มิฉะนั้นจะทำงานได้ดี
Mayank Pratap Singh

สิ่งนี้ช้าเกินไปหากคุณมีบันทึกเป็นล้านรายการ
ช่วงเวลาสั้น ๆ

@jifferent แน่นอน! ฉันเพิ่มเข้าไปเพื่อแสดงว่าโซลูชันของฉันคืออะไรในขณะที่โพสต์คำถาม คำตอบอื่น ๆ ดีกว่ามาก
Oli

0

นี่คือวิธีที่คุณสามารถจัดกลุ่มข้อมูลตามช่วงเวลาที่กำหนด:

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))

0

ฉันมีตารางคำสั่งซื้อในฐานข้อมูลของฉัน ฉันจะนับคำสั่งซื้อต่อเดือนในช่วง 3 เดือนที่ผ่านมา

from itertools import groupby
from dateutil.relativedelta import relativedelta

date_range = datetime.now()-relativedelta(months=3)
aggs =Orders.objects.filter(created_at=date_range)\
            .extra({'date_created':"date(created_at)"}).values('date_created')

for key , group in groupby(aggs):
     print(key,len(list(group)))

created_at คือฟิลด์วันที่และเวลา โดยฟังก์ชันพิเศษสิ่งที่ทำคือการรับวันที่จากค่าวันที่และเวลา เมื่อใช้วันที่และเวลาเราอาจนับไม่ถูกต้องเนื่องจากวัตถุถูกสร้างขึ้นในเวลาที่ต่างกันในหนึ่งวัน

สำหรับลูปจะพิมพ์วันที่และจำนวนการนับ


-1

ตามเดือน:

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

ตามปี:

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

ตามวัน:

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

อย่าลืมนำเข้า Count

from django.db.models import Count

สำหรับ django <1.10


3
ใช่วิธีปฏิบัติที่ดีนำเข้าทั้งหมดจากรุ่น
JC Rocamonde

ฉันเห็นได้ชัดว่ากำลังแดกดัน มันเป็นวิธีปฏิบัติที่น่าสยดสยองที่จะทำเช่นนั้น คุณไม่ควรทำและฉันจะลดคะแนนสำหรับสิ่งนั้น (ซึ่งฉันไม่ได้ทำ)
JC Rocamonde
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.