Django ORM如何对平均值结果进行四舍五入

23

我有一个模型,其中使用Django ORM从表格中提取平均值。 我想要对该平均值四舍五入,应该怎么做?

如下所示,我正在从价格模型中按日期(格式为YYYY-MM)分组提取平均价格,我希望自动提取最接近的平均值并将其四舍五入。

rs = Prices.objects.all.extra(select={
    'for_date': 'CONCAT(CONCAT(extract( YEAR from for_date ), "-"),
        LPAD(extract(MONTH from for_date), 2, "00"))'
    }).values('for_date').annotate(price=Avg('price')).order_by('-for_date')

你是偶然解决了这个问题吗? - Nicholas Hamilton
1
更新:类似于给出答案的函数已添加到Django v2.2中,名称为Round - Ralf
5个回答

48

使用Func()表达式

下面是一个示例,使用来自Django聚合主题指南的Book模型在SQLite中四舍五入到小数点后两位:

class Round(Func):
    function = 'ROUND'
    template='%(function)s(%(expressions)s, 2)'

Book.objects.all().aggregate(Round(Avg('price')))

这允许轮函数进行参数化(来自@RichardZschech的答案):

class Round(Func):
  function = 'ROUND'
  arity = 2

Book.objects.all().aggregate(Round(Avg('price'), 2))

2
根据您的注释或聚合的复杂程度,您可能需要分配一个别名。例如: Book.objects.all().aggregate(rounded_avg_price=Round(Avg('price'))) - Duncan
@Blairg23,感谢您的编辑!但是您不觉得粗体和大标题让文本有些过于突兀吗?在我的经验中,在StackOverflow中使用粗体和大标题是比较少见的,它们主要用于构建更长的答案。 - mrts
@mrts 我只是准备让答案更长一些! ;) 但同时,我认为表述得更明确从来不是坏事 :) - Blairg23
1
@Blairg23,谢谢,一切都好了!关于1.11文档——我认为仅链接到2.1文档更加简洁。API的相关部分是相同的,我们可以假设人们足够聪明,如果需要,可以切换到1.11。 - mrts
我使用这个解决方案得到了“TypeError: 复杂聚合需要别名”的错误,但是找不到任何解决方法 :( - Macilias
显示剩余3条评论

22

在之前的回答基础上,我找到了这个解决办法,能够使其适用于PostgreSQL

from django.db.models import Func

class Round2(Func):
    function = "ROUND"
    template = "%(function)s(%(expressions)s::numeric, 2)"

# Then use it as ,e.g.:
# queryset.annotate(ag_roi=Round2("roi"))

# qs.aggregate(ag_sold_pct=Round2(Sum("sold_uts") / (1.0 * Sum("total_uts"))) * 100

16

在 @mrts 的答案基础上进行改进。

这使得 round 函数可以被参数化:

class Round(Func):
  function = 'ROUND'
  arity = 2

Book.objects.all().aggregate(Round(Avg('price'), 2))

1
在PostgreSQL中这不起作用。有没有办法在psql中使用arity? - rain01
我更新了你的答案,同时将其添加到得票更多的答案中,并标记该答案为被接受的答案。 - Blairg23
1
我遇到了这个问题:HINT: No function matches the given name and argument types. You might need to add explicit type casts.``` msonsona的解决方案对我有用,但是不能与arity一起使用。 - rain01
@rain01,你可以尝试将舍入值改为2.0而不是2。请参考https://dev59.com/Vmcs5IYBdhLWcg3wHwbU - Richard Zschech

6

Django拥有Round函数。有关详细信息,请参见文档


3
此函数可在 Django >= 2.2 版本中使用。在 3.3 开发者版本中,他们增加了 precision 参数。 - Tobit

5

我需要同时支持PostgreSQLSQLite,并保留指定保留位数的能力。

基于之前的答案构建:

class Round(Func):
    function = 'ROUND'
    arity = 2
    # Only works as the arity is 2
    arg_joiner = '::numeric, '

    def as_sqlite(self, compiler, connection, **extra_context):
        return super().as_sqlite(compiler, connection, arg_joiner=", ", **extra_context)

# Then one can use it as:
# queryset.annotate(avg_val=Round(AVG("val"), 6))

我更喜欢一些更干净的东西,比如

if SQLITE:
    arg_joiner=", "
elif PGSQL:
    arg_joiner = '::numeric, '
else raise NotImplemented()

但是我没有找到如何做到这一点,随意改进!

谢谢,这是一个很好的答案。 - Siraj Alam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接