【问题标题】:django annotate and count: how to filter the ones to include in countdjango 注释和计数:如何过滤要包含在计数中的那些
【发布时间】:2015-06-09 00:38:35
【问题描述】:

给定一个查询集,我将相关对象 (ModelA) 的计数添加到以下内容:

qs = User.objets.all()
qs.annotate(modela__count=models.Count('modela'))

但是,有没有办法计算仅满足条件的 ModelA?例如,计算deleted_at为null的ModelA?

我尝试了两种无法正常工作的解决方案。

1) 正如@knbk 建议的那样,在注释之前使用过滤器。

qs = User.objects.all().filter(modela__deleted_at__isnull=True).annotate(modela__count=models.Count('modela', distinct=True))

这里是 django 生成的查询的简化版本:

SELECT COUNT(DISTINCT "modela"."id") AS "modela__count", "users".*
FROM "users"
LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
WHERE "modela"."deleted_at" IS NULL 
GROUP BY "users"."id"

问题来自 WHERE 子句。 事实上,有一个 LEFT JOIN,但后来的 WHERE 条件迫使它成为一个普通的 JOIN。我需要将条件拉到 JOIN 子句中以使其按预期工作。

所以,而不是

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
WHERE "modela"."deleted_at" IS NULL

当我直接在普通 SQL 中执行它时,我需要以下内容。

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
AND "modela"."deleted_at" IS NULL

如何在不进行原始查询的情况下更改查询集以获取此信息?

2) 正如其他人所建议的,我可以使用条件聚合。

我尝试了以下方法:

qs = User.objects.all().annotate(modela__count=models.Count(Case(When(modela__deleted_at__isnull=True, then=1))))

变成下面的 SQL 查询:

SELECT COUNT(CASE WHEN "modela"."deleted_at" IS NULL THEN 1 ELSE NULL END) AS "modela__count", "users".*
FROM "users" LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" )
GROUP BY "users"."id"

通过这样做,我得到了所有用户(因此 LEFT JOIN 正常工作),但对于根本没有任何 ModelA 的所有用户,modela__count 得到“1”(而不是 0)。 如果没有什么可数的,为什么我得到 1 而不是 0? 怎么改?

【问题讨论】:

    标签: django django-models django-queryset


    【解决方案1】:

    LEFT JOIN 中,modela 的每个字段都可能是NULL,因为没有相应的行。所以

    modela.deleted_at IS NULL
    

    ...不仅适用于匹配的行,而且适用于那些没有对应的modela 行的users

    我认为正确的 SQL 应该是:

    SELECT COUNT(
        CASE
          WHEN
            `modela`.`user_id` IS NOT NULL  -- Make sure modela rows exist
            AND `modela`.`deleted_at` IS NULL
            THEN 1
          ELSE NULL
        END
      ) AS `modela__count`,
      `users`.*
    FROM `users`
    LEFT OUTER JOIN `modela`
      ON ( `users`.`id` = `modela`.`user_id` )
    GROUP BY `users`.`id`
    

    在 Django 1.8 中,这应该是:

    from django.db import models
    qs = User.objects.all().annotate(
        modela_count=models.Count(
            models.Case(
                models.When(
                    modela__user_id__isnull=False,
                    modela__deleted_at__isnull=True,
                    then=1,
                )
            )
        )
    )
    

    通知

    @YAmikep 发现Django 1.8.0 中的一个错误 使生成的 SQL 具有 INNER JOIN 而不是 LEFT JOIN,因此您将丢失没有对应外键关系的行。 使用 Django 1.8.2 或更高版本 来解决这个问题。

    【讨论】:

    • 感谢您的留言。使用 django ORM,生成的 SQL 查询使用 INNER JOIN 而不是 LEFT JOIN,这不起作用,因为并非所有用户都在结果中。
    • @YAmikep 我不这么认为。我在最近的项目中使用了这种技术,一切正常。此外,我在 Python 交互式解释器中使用print qs.query 仔细检查了生成的 SQL,并确认它是由 Django ORM 生成的LEFT JOIN。如果你的项目还是有问题,可能你应该检查一下是不是其他原因造成的。
    • 我也使用了print qs.query。我仔细检查并确认 INNER JOIN 是由 Django ORM 生成的。不知道为什么我们不会得到同样的东西。这很奇怪。我也使用 django 1.8。
    • 尝试使用 Django 1.8.2
    • 你是对的,它似乎在 1.8.2 中修复了:code.djangoproject.com/ticket/24766
    【解决方案2】:

    在 Django 1.8 中,我相信这可以通过 conditional aggregation 来实现。但是对于以前的版本,我会使用 .extra

    ModelA.objects.extra(select={
        'account_count': 'SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'
    })
    

    【讨论】:

    • 谢谢。我已经更新到 django 1.8。我正在尝试使用条件聚合但无法使其工作。我用我尝试过的内容更新了我的帖子。我可能误解了如何使用条件聚合。有什么想法吗?
    • 一个快速的,我不确定它是否会起作用 -> 尝试使用 models.Sum 而不是 models.Count
    • 好的,我刚刚尝试使用Sum。它似乎有效,除了我得到1 的所有应该是0 的地方。我尝试在案例中使用default=Value(0),但它并没有改变任何东西。 ://
    • 我也尝试了Coalesce,但没有成功。这可能是不可能的。
    • 我也很惊讶。您是否真的获得了具有 NULL prop1 的帐户的 ModelA 对象。你能看到正在生成的查询吗? Django 是使用 LEFT 还是 INNER JOIN?
    【解决方案3】:

    您可以在注释之前简单地过滤:

    from django.db.models import Q, Count
    
    qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))
    

    【讨论】:

    • 我不确定这是否可行,但似乎可行。我认为这样做不会返回所有 ModelA 对象,因为过滤器位于 ModelA 管理器上。我一定想念什么。谢谢
    • 它实际上不能正常工作。我看到了这样做的问题。由于过滤器(where 子句),第一个模型上的 LEFT JOIN 不能正常工作,所以我没有得到所有的 ModelA 对象。我用更详细的示例更新了我的问题。
    猜你喜欢
    • 2012-03-07
    • 1970-01-01
    • 2015-08-25
    • 2019-06-06
    • 2011-03-31
    • 2023-02-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    相关资源
    最近更新 更多