Django的.annotate()能返回对象吗?

4 投票
3 回答
10837 浏览
提问于 2025-04-16 16:02

我有一组记录,长得像这样(为了不让人觉得无聊,省略了一些多余的数据):

 id | user_id |            created            | units
----+---------+-------------------------------+-------
  1 |       1 | 2011-04-18 15:43:02.737063+00 |    20
  2 |       1 | 2011-04-18 15:43:02.737063+00 |     4
  3 |       1 | 2011-04-18 15:46:48.592999+00 |    -1
  4 |       1 | 2011-04-19 12:02:10.687587+00 |    -1
  5 |       1 | 2011-04-19 12:09:20.039543+00 |    -1
  6 |       1 | 2011-04-19 12:11:21.948494+00 |    -1
  7 |       1 | 2011-04-19 12:15:51.544394+00 |    -1
  8 |       1 | 2011-04-19 12:16:44.623655+00 |    -1

我想要的结果是这样的:

 id | user_id |            created            | units
----+---------+-------------------------------+-------
  8 |       1 | 2011-04-19 12:16:44.623655+00 |    14
  2 |       1 | 2011-04-18 15:43:02.737063+00 |     4

所以我自然想到了 .annotate() 这个方法:

u = User.objects.get(pk=1)
(MyModel.objects.filter(user=u)
        .values("id","user","created")
        .annotate(stuff=Sum("units"))
)

问题是,我想要的是对象,而不是一堆字典的列表。我需要这些对象的方法可以用。有没有什么办法可以做到这一点?

补充说明: 我应该提到我试过使用 .values(),因为如果不使用它,我确实会得到一些带注释的对象,但会有8个(就像上面的第一个查询),而不是2个(就像上面的第二个结果)。我猜是因为有一个时间戳在里面,让这些行变得不同,所以没有合并它们:

MyModel.objects.filter(user=u).annotate(Sum("units"))
# Returns 8 records, not 2 as expected

3 个回答

0

Annotate会返回一个查询集,但你同时调用了.values(),这总是会返回一个字典。

3

问题不在于 annotate,而是在于对 values 的调用。文档上很清楚地说明了 annotate 是在查询集上工作的。是 values 的调用返回了一个值的字典,这样你就无法访问模型了。

补充说明:如果你只想获取某些字段,但仍然想保留模型,可以使用 defer,而不是 values。

7

看起来我最开始提问的时候有些混淆,结果我被建议不要使用 .values(),但其实那并不是问题所在。真正的问题是,Django 的 .annotate() 方法不允许用计算出来的值来覆盖列的属性,它只是给对象添加额外的数据。这其实是有道理的,因为如果你想要返回一个对象,你希望这个对象能代表数据库中的一行,而不是一些计算值混在一起覆盖了列的值。

不过,这对我来说并不奏效,因为上面的功能意味着像 created(一个时间戳)这样的列,我无法得到我想要的计算值,除非使用 .values(),而这又不能给我对象。

所以,我选择了下一个最好的办法:使用 .raw()

query = """
    SELECT
        ep.product_id              AS product_id,
        ep.user_id                 AS user_id,
        MAX(ep.created)            AS created,
        SUM(eup.units)             AS units
    FROM
        ecom_unitpermission AS eup
    WHERE
        ep.user_id = 1
    GROUP BY
        ep.product_id,
        ep.user_id
"""

for perm in MyModel.objects.raw(query):
    print "%15s %3s %s" % (perm.product.name, perm.units, perm.product.somemethod())

使用 .defer() 可能也能解决这个问题,不过 在 Django 1.3 中,结合使用 .defer().annotate() 似乎有个 bug

撰写回答