📜  django orm 按月和年分组 - Python (1)

📅  最后修改于: 2023-12-03 15:00:26.544000             🧑  作者: Mango

Django ORM 按月和年分组

在 Django 中,ORM(对象关系映射)是让我们能够用 Python 代码来操作数据库的一种方式。通过 Django ORM,我们可以很方便地使用 SQL 语言进行操作,而不用直接编写 SQL 代码。

有时候我们需要按照一定的规则对数据库的数据进行分组,例如按照月份或年份进行分组以统计数据。这篇文章将介绍如何使用 Django ORM 对数据库数据按照月份和年份进行分组。

按月份进行分组
数据库准备

首先,我们创建一个简单的名为 Order 的模型,来表示订单:

from django.db import models

class Order(models.Model):
    order_date = models.DateField()
    amount = models.DecimalField(max_digits=5, decimal_places=2)

每一条订单记录包含一个 order_date 属性表示订单的日期,和一个 amount 属性表示订单的总金额。

我们可以使用 Django 自带的 SQLite3 数据库来进行测试,因为它是一个小型的、易于部署和测试的数据库。在设置文件中,设置数据库为 SQLite3:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

我们可以使用 Django Admin 界面或 Django Shell 来生成一些数据:

import datetime
from random import randint
from myapp.models import Order

for i in range(60):
    order_date = datetime.date(2021, randint(1, 12), randint(1, 28))
    amount = randint(20, 60)
    Order.objects.create(order_date=order_date, amount=amount)

这段代码会往数据库中插入60条记录,其中订单的日期在2021年内随机生成,金额在20到60之间随机生成。

使用 Django ORM 进行查询

我们可以使用 Django ORM 来按月份进行分组,代码如下:

from django.db.models.functions import ExtractMonth, TruncMonth
from django.db.models import Sum

orders_by_month = Order.objects.annotate(
    month=TruncMonth('order_date')
).values(
    'month'
).annotate(
    total=Sum('amount')
).order_by('-month')

for item in orders_by_month:
    print(f"{item['month'].strftime('%Y-%m')}: {item['total']}")

上面的代码首先用 annotate() 方法添加一个名为 month 的属性,该属性值为订单日期的月份的第一天。具体来说,它调用了 TruncMonth 函数来截取日期、月份,并只保留月份的信息。

然后,我们使用 values() 方法来指定返回的结果集只包含 month 属性。这是必须的,因为我们正在进行分组。

最后,我们使用 annotate()Sum() 方法根据 month 属性对订单金额进行求和,并按照降序对 month 属性进行排序。这样我们就可以得到一个按照月份进行分组的结果集。

输出 Markdown 格式的结果
## 订单汇总

### 按月份汇总

| 日期 | 总金额 |
| --- | ---: |
{% for item in orders_by_month %}
| {{ item.month|date:"Y-m" }} | {{ item.total }} |
{% empty %}
没有数据。
{% endfor %}

上面是输出 Markdown 格式的结果的模板。我们在 Django 中使用模板引擎来生成最终的 Markdown 格式的文本。渲染模板的例子如下:

from django.template import Template, Context

template_string = """
## 订单汇总

### 按月份汇总

| 日期 | 总金额 |
| --- | ---: |
{% for item in orders_by_month %}
| {{ item.month|date:"Y-m" }} | {{ item.total }} |
{% empty %}
没有数据。
{% endfor %}
"""

template = Template(template_string)
context = Context({'orders_by_month': orders_by_month})

markdown_text = template.render(context)
print(markdown_text)

渲染完成后,我们就可以得到一个 Markdown 格式的文本,它包含了按月份进行分组的汇总数据。

按年份进行分组

如果我们需要按年份进行分组,则可以按照类似的方式编写代码。这里我们先创建一个名为 YearlyReport 的模型,表示每年的报告数据。

class YearlyReport(models.Model):
    year = models.IntegerField()
    total_amount = models.DecimalField(max_digits=10, decimal_places=2)
    total_count = models.IntegerField()

我们可以使用 SQL 语句来生成数据:

INSERT INTO myapp_yearlyreport (id, year, total_amount, total_count) VALUES (1, 2021, 2306.34, 60);
INSERT INTO myapp_yearlyreport (id, year, total_amount, total_count) VALUES (2, 2020, 2101.78, 65);
INSERT INTO myapp_yearlyreport (id, year, total_amount, total_count) VALUES (3, 2019, 1842.18, 66);
INSERT INTO myapp_yearlyreport (id, year, total_amount, total_count) VALUES (4, 2018, 1745.76, 71);

然后,我们可以使用 Django ORM 来进行查询:

from django.db.models.functions import ExtractYear, TruncYear
from django.db.models import Sum, Count

reports_by_year = YearlyReport.objects.annotate(
    year=TruncYear('year')
).values(
    'year'
).annotate(
    total_amount=Sum('total_amount'),
    total_count=Count('id')
).order_by('-year')

for item in reports_by_year:
    print(f"{item['year'].strftime('%Y')}: {item['total_amount']}, {item['total_count']} orders")

上面的代码与按月份进行分组的代码很相似。我们使用 annotate() 方法添加一个名为 year 的属性,该属性值为年份的第一天。然后,我们使用 values() 方法来指定返回的结果集只包含 year 属性。最后,我们使用 annotate()Sum() 方法对 total_amount 属性进行求和,并对 id 属性进行计数,这样我们就可以得到一个按照年份进行分组的结果集。

最终,我们可以使用类似上面的方式来输出 Markdown 格式的文本。

总结

在这篇文章中,我们介绍了如何使用 Django ORM 对数据库数据按照月份和年份进行分组。我们使用了 Django 内置的时间函数和聚合函数来实现这个功能。同时,我们使用了 Django 的模板引擎来生成最终的 Markdown 格式的文本。

Django ORM 是一个非常强大的工具,它使得开发者可以很方便地操作数据库。希望本文对你有所帮助,祝你在 Django 的开发之路上越走越好!