在 Django 中按日期分组

Group by date in Django

我正在尝试实现以下 SQL 查询的结果

SELECT 
UNIX_TIMESTAMP(DATE((FROM_UNIXTIME(`timestamp`)))) AS `x`, 
COUNT(`timestamp`) as y 
    FROM somedb.events
        WHERE user_id=3 AND 
        `timestamp` > 1612117800 AND 
        `timestamp` < 1614450600 AND
        `kind`='food'
    GROUP BY `x`
    ORDER BY `x` desc;

使用 Django ORM。预期输出:

[
    {
        "x": 1613759400,
        "y": 2
    },
    {
        "x": 1612463400,
        "y": 1
    }
]

这是我到目前为止尝试过的:

queryset = events.objects.filter(
            user=request.user,
            timestamp__range=dates
        )

第一种方法:

result = queryset.annotate(
            trunc_date_timestamp=Func(
              Value(
                TruncDate(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                    )
                )
              ),
              function='UNIX_TIMESTAMP',
              output_field=models.IntegerField()
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

这会产生输出:

[
    {
        "x": 0,
        "y": 3
    }
]

第二种方法:

result = queryset.annotate(
            trunc_date_timestamp=Func(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                        output_field=models.DateField()
                    ),
                function='UNIX_TIMESTAMP'
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

产生输出:

[
    {
        "x": 1613831760,
        "y": 1
    },
    {
        "x": 1613810160,
        "y": 1
    },
    {
        "x": 1612520520,
        "y": 1
    }
]

我终于成功了。我在这两种方法中都犯了小错误。

第一种方法:

result = ueryset.annotate(
            trunc_date_timestamp=Func(
                TruncDate(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                        output_field=models.DateField() # <<--- This is must
                    )
                ),
                function='UNIX_TIMESTAMP',
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

删除了 Value() API 并将 output_field=models.DateTimeField() 添加到内部 Func() API。 output_field 将确保由内部 Func() API 编辑的字段 return 必须是 models.DateField() 类型,TruncDate 将应用于该字段。

第二种方法:

queryset = queryset.annotate(
            trunc_date_timestamp=Func(
                Cast(
                    Func(
                        F('timestamp'),
                        function='FROM_UNIXTIME',
                    ),
                    output_field=models.DateField()
                ),
                function='UNIX_TIMESTAMP'
            )
        ).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')

本来以为

Func(
    F('timestamp'),
    function='FROM_UNIXTIME',
    output_field=models.DateField()
)

这将 return 一个 models.DateField() 类型的字段,但是 我不知道为什么它不工作而且失败了!!! 所以我改用 Cast() 方法并将 returned 表达式转换为 models.DateField() 以使其工作。

虽然这个解决方案有效,但我强烈认为第二种方法的原始代码也应该有效,因为如果我正确理解 Func() 表达式,那么

Func(
   F('timestamp'),
   function='FROM_UNIXTIME',
   output_field=models.DateField()
) 

Cast(
    Func(
       F('timestamp'),
       function='FROM_UNIXTIME'
    ),
    output_field=models.DateField()
)

应该会产生相同的结果。