django 注释和计数:如何过滤要包含在计数中的那些
django annotate and count: how to filter the ones to include in count
给定一个查询集,我将相关对象 (ModelA) 的计数添加如下:
qs = User.objets.all()
qs.annotate(modela__count=models.Count('modela'))
但是,有没有办法统计只满足一个条件的ModelA呢?例如,计算 deleted_at 为空的 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?
如何改变?
您可以在注释之前简单地过滤:
from django.db.models import Q, Count
qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))
在 Django 1.8 中,我相信这可以通过 conditional aggregation . However for previous versions I would do it with .extra
实现
ModelA.objects.extra(select={
'account_count': 'SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'
})
在LEFT JOIN
中,modela
的每个字段都可能是NULL
,因为没有相应的行。所以
modela.deleted_at IS NULL
...不仅适用于匹配的行,而且适用于那些 users
没有对应的 modela
行的行。
我认为正确的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 或更高版本 修复该问题。
给定一个查询集,我将相关对象 (ModelA) 的计数添加如下:
qs = User.objets.all()
qs.annotate(modela__count=models.Count('modela'))
但是,有没有办法统计只满足一个条件的ModelA呢?例如,计算 deleted_at 为空的 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?
如何改变?
您可以在注释之前简单地过滤:
from django.db.models import Q, Count
qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))
在 Django 1.8 中,我相信这可以通过 conditional aggregation . However for previous versions I would do it with .extra
ModelA.objects.extra(select={
'account_count': 'SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'
})
在LEFT JOIN
中,modela
的每个字段都可能是NULL
,因为没有相应的行。所以
modela.deleted_at IS NULL
...不仅适用于匹配的行,而且适用于那些 users
没有对应的 modela
行的行。
我认为正确的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 或更高版本 修复该问题。