通过反向外键注释 Django 查询集
Annotating Django query sets through reverse foreign keys
给定一组简单的模型如下:
class A(models.Model):
pass
class B(models.Model):
parent = models.ForeignKey(A, related_name='b_set')
class C(models.Model):
parent = models.ForeignKey(B, related_name='c_set')
我想创建一个带有两个注释的 A
模型的查询集。一个注释应该是 B
行的数量,其中 A
行作为其父级。另一个注释应该表示 B
行的数量,再次以 A
对象作为父对象,它们的 [= 中至少有 n
个 C
类型的对象21=].
例如,考虑以下数据库和n = 3
:
Table A
id
0
1
Table B
id parent
0 0
1 0
Table C
id parent
0 0
1 0
2 1
3 1
4 1
我希望能够得到 [(0, 2, 1), (1, 0, 0)]
形式的结果,因为 ID 为 0 的 A
对象有两个 B
对象,其中一个对象至少有三个相关 C
个对象。 ID 为 1 的 A
对象没有 B
个对象,因此也没有 B
个至少有三个 C
行的对象。
第一个注释很简单:
A.objects.annotate(annotation_1=Count('b_set'))
我现在要设计的是第二个注解。我设法计算了每个 A
的 B
行数,其中 B
对象至少有一个 C
对象,如下所示:
A.objects.annotate(annotation_2=Count('b_set__c_set__parent', distinct=True))
但我想不出一种方法来使用除一个之外的最小相关集大小。希望这里有人能指出我正确的方向。我想到的一种方法是以某种方式注释查询中的 B
对象而不是 A
行,这是注释方法的默认设置,但我找不到任何关于此的资源。
这是 Django 1.11 限制下的复杂查询。我决定通过两个查询来完成,并将结果合并到一个列表中,该列表可以像查询集一样被视图使用:
from django.db.models import Count
sub_qs = (
C.objects
.values('parent')
.annotate(c_count=Count('id'))
.order_by()
.filter(c_count__gte=n)
.values('parent')
)
qs = B.objects.filter(id__in=sub_qs).values('parent_id').annotate(cnt=Count('id'))
qs_map = {x['parent_id']: x['cnt'] for x in qs}
rows = list(A.objects.annotate(annotation_1=Count('b_set')))
for row in rows:
row.annotation_2 = qs_map.get(row.id, 0)
列表rows
就是结果。比较复杂的qs.query编译成相对简单的SQL:
>>> print(str(qs.query))
SELECT app_b.parent_id, COUNT(app_b.id) AS cnt
FROM app_b
WHERE app_b.id IN (
SELECT U0.parent_id AS Col1 FROM app_c U0
GROUP BY U0.parent_id HAVING COUNT(U0.id) >= 3
)
GROUP BY app_b.parent_id; -- (added white space and removed double quotes)
这个简单的解决方案可以更容易地修改和测试。
注意:一个查询的解决方案也存在,但似乎没有用。原因:需要 Subquery 和 OuterRef()
。它们很棒,但是通常 Count()
来自聚合的查询不支持与连接解析一起编译的查询。一个子查询可以通过lookup ...__in=...
来分离,可以被Django 编译,但是这样就无法使用OuterRef()
。如果它是在没有 OuterRef()
的情况下编写的,那么它是一个非常复杂的非最佳嵌套 SQL 时间复杂度可能是 O(n2) A
table 用于许多(或所有)数据库后端。未测试。
给定一组简单的模型如下:
class A(models.Model):
pass
class B(models.Model):
parent = models.ForeignKey(A, related_name='b_set')
class C(models.Model):
parent = models.ForeignKey(B, related_name='c_set')
我想创建一个带有两个注释的 A
模型的查询集。一个注释应该是 B
行的数量,其中 A
行作为其父级。另一个注释应该表示 B
行的数量,再次以 A
对象作为父对象,它们的 [= 中至少有 n
个 C
类型的对象21=].
例如,考虑以下数据库和n = 3
:
Table A
id
0
1
Table B
id parent
0 0
1 0
Table C
id parent
0 0
1 0
2 1
3 1
4 1
我希望能够得到 [(0, 2, 1), (1, 0, 0)]
形式的结果,因为 ID 为 0 的 A
对象有两个 B
对象,其中一个对象至少有三个相关 C
个对象。 ID 为 1 的 A
对象没有 B
个对象,因此也没有 B
个至少有三个 C
行的对象。
第一个注释很简单:
A.objects.annotate(annotation_1=Count('b_set'))
我现在要设计的是第二个注解。我设法计算了每个 A
的 B
行数,其中 B
对象至少有一个 C
对象,如下所示:
A.objects.annotate(annotation_2=Count('b_set__c_set__parent', distinct=True))
但我想不出一种方法来使用除一个之外的最小相关集大小。希望这里有人能指出我正确的方向。我想到的一种方法是以某种方式注释查询中的 B
对象而不是 A
行,这是注释方法的默认设置,但我找不到任何关于此的资源。
这是 Django 1.11 限制下的复杂查询。我决定通过两个查询来完成,并将结果合并到一个列表中,该列表可以像查询集一样被视图使用:
from django.db.models import Count
sub_qs = (
C.objects
.values('parent')
.annotate(c_count=Count('id'))
.order_by()
.filter(c_count__gte=n)
.values('parent')
)
qs = B.objects.filter(id__in=sub_qs).values('parent_id').annotate(cnt=Count('id'))
qs_map = {x['parent_id']: x['cnt'] for x in qs}
rows = list(A.objects.annotate(annotation_1=Count('b_set')))
for row in rows:
row.annotation_2 = qs_map.get(row.id, 0)
列表rows
就是结果。比较复杂的qs.query编译成相对简单的SQL:
>>> print(str(qs.query))
SELECT app_b.parent_id, COUNT(app_b.id) AS cnt
FROM app_b
WHERE app_b.id IN (
SELECT U0.parent_id AS Col1 FROM app_c U0
GROUP BY U0.parent_id HAVING COUNT(U0.id) >= 3
)
GROUP BY app_b.parent_id; -- (added white space and removed double quotes)
这个简单的解决方案可以更容易地修改和测试。
注意:一个查询的解决方案也存在,但似乎没有用。原因:需要 Subquery 和 OuterRef()
。它们很棒,但是通常 Count()
来自聚合的查询不支持与连接解析一起编译的查询。一个子查询可以通过lookup ...__in=...
来分离,可以被Django 编译,但是这样就无法使用OuterRef()
。如果它是在没有 OuterRef()
的情况下编写的,那么它是一个非常复杂的非最佳嵌套 SQL 时间复杂度可能是 O(n2) A
table 用于许多(或所有)数据库后端。未测试。