获取每个月最后一条记录的值(Django)

Posted

技术标签:

【中文标题】获取每个月最后一条记录的值(Django)【英文标题】:Get the value of last record of every month (Django) 【发布时间】:2017-02-19 21:29:03 【问题描述】:

我的模特:

class Transaction (models.Model):
    transaction_id = models.AutoField(primary_key=True)
    net_monthly_transaction = models.DecimalField(max_digits = 10, decimal_places = 2, default=0)
    # deposit or withdrawal (withdrawal with negative value)
    amount = models.DecimalField(max_digits = 10, decimal_places = 2)
    time_stamp = models.DateTimeField(default=datetime.now, blank=True)


    def __str__(self):              # __unicode__ on Python 2
        return str(self.time_stamp)  + str(self.amount) + str(self.net_monthly_transaction)

我的目标是从每个月的最后一个条目中获取 net_monthly_transaction 的值。

在 S.O. 的帮助下我已经做到了这一点:

truncate_date = connection.ops.date_trunc_sql('month', 'time_stamp')
        lem = Transaction.objects.extra('month':truncate_date).values('month').annotate(last_record=Max('time_stamp')).values_list('net_monthly_transaction', flat=True)

上面的查询假设是从每个月的 max time_stamp 中获取 net_monthly_transaction 的值。

但事实并非如此。

如果我一个接一个地为十月创建三个条目:

    net_monthly_transaction = 3000 net_monthly_transaction = 4000 net_monthly_transaction = 5000

查询将返回所有 3 个值。

另一方面:

    net_monthly_transaction = 3000 net_monthly_transaction = 2000 net_monthly_transaction = 1000

那么只返回值 3000。

因此,根据 net_monthly_transaction 的大小在某处设置了条件。我对如何解决这个问题有点迷茫。

有人可以提供一些方向吗?

提前致谢。

【问题讨论】:

你使用的是什么版本的 Django?答案因人而异 @TitusP 我正在使用最新的 django 和最新的 python。 【参考方案1】:

如果要获取当月的最后一条记录,首先过滤该月的所有记录,然后按升序排列,选择最后一条记录,应该是最新的记录。以下查询应按此顺序运行。

Transaction.objects.filter(time_stamp__month=month_you_are_checking).order_by('time_stamp').last()

附:未测试。

【讨论】:

我说的不是一个月,而是每个月。这就是为什么需要截断,但在 Django 中它变得有点模糊。【参考方案2】:

我会通过使用两个查询集来解决这个问题(除非下面的更简单的方法是一个选项)。只要您不明确评估 last_entries,这将在您评估 transactions 时导致单个查询。

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

# Selects last time_stamp for each month
last_entries = (Transaction.objects
    .annotate(tx_month=TruncMonth('time_stamp'))
    .values('tx_month')
    .annotate(last_entry=Max('time_stamp'))
    .values_list('last_entry', flat=True))

# Selects transactions with time_stamps matching last_entries
# ie. last transaction in each month
transactions = Transaction.objects.filter(
    time_stamp__in=last_entries
)

transactions 是一个普通的查询集,包含每个月的最后一个 Transaction 实例。如果您想要一个简单的 net_monthly_transaction 值列表,没有其他信息,您可以添加:

net_values = transactions.values_list(
    'net_monthly_transaction', flat=True
)

需要非常小心的一点是,如果两个条目具有相同的时间戳,那么它们都会出现在结果集中。

更简单的方法

如果net_monthly_transaction 只是给定月份所有amounts 的总和,那么您可以使用类似的东西来代替上面的内容

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

transactions = (Transaction.objects
    .annotate(month=TruncMonth('time_stamp'))
    .values('month')
    .annotate(month_net=Sum('amount')))

现在transactions 包含代表每个月最后一笔交易的字典。每个字典都有一个包含月份的month 键和一个包含当月净交易的month_net 键。作为奖励,您不必担心具有相同时间戳的条目。

当然,如果net_monthly_transaction 是更复杂计算的结果,那么这可能不是一个选项。

您的初始方法

您的初始查询无法正常工作有两个主要原因。

    这种查询通常依赖于将表连接到自身或 WHERE 子句中的子查询。我不知道使用 Django 的 ORM 使用单个查询集执行其中任何一个的好方法,除非您诉诸于在 extra() 中填充原始 SQL 或类似的东西。但是,如果您按照我们上面的方式使用两个查询集,则生成 WHERE 子句子查询是微不足道的。

    这样使用values_list()没有意义

    truncate_date = connection.ops.date_trunc_sql('month', 'time_stamp')
    lem = Transaction.objects.extra('month':truncate_date).values('month').annotate(last_record=Max('time_stamp'))
    

    到目前为止,它类似于上面使用的last_entries 查询集。我们正在为每个月份选择唯一的月份值和 last time_stamp。

    当我们添加 .values_list('net_monthly_transaction', flat=True) 时,我们告诉查询构建器我们只关心 net_monthly_transaction 字段,因此它会丢弃所有其他内容并生成类似这样的内容

    SELECT "transaction"."net_monthly_transaction"
    FROM "transaction"
    GROUP BY "transaction"."net_monthly_transaction"
    

    GROUP BYextraannotate 调用中唯一剩下的东西,即使它也已更改,因此它不会做我们希望它做的事情。

【讨论】:

您更简单的方法有效,我可以安全地删除“net_monthly_transaction”字段。到目前为止,这是完美的。你的解释对理解很有帮助,谢谢凯文。

以上是关于获取每个月最后一条记录的值(Django)的主要内容,如果未能解决你的问题,请参考以下文章

获取每个月的最后一条记录

为每个 ID 选择每个月的最后一条记录

获取每个 ID 的最后一条记录

获取查询集中的最后一条记录

获取表中存在的每个日期的第一条和最后一条记录号

获取分组后取某字段最大一条记录(求每个类别中最大的值的列表)