给出一组简单的模型如下:
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
类型为c_set
n = 3
1}}。
例如,请考虑以下数据库和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)]
我希望能够获得A
形式的结果,因为id为0的B
对象有两个C
个对象,其中一个至少有三个相关的A
个对象。 ID为1的B
对象没有B
个对象,因此也没有C
个对象,且至少有三个A.objects.annotate(annotation_1=Count('b_set'))
行。
第一个注释很简单:
B
我现在想要设计的是第二个注释。我已设法计算A
对象至少有一个B
对象的每C
行的A.objects.annotate(annotation_2=Count('b_set__c_set__parent', distinct=True))
行数,如下所示:
B
但我无法找到一种方法来做到最小相关的设置大小而不是一个。希望有人能指出我正确的方向。我想到的一种方法是以某种方式注释查询中的A
对象而不是{{1}}行,这是annotate方法的默认值,但我找不到任何资源。
答案 0 :(得分:1)
这是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()
聚合不会被与连接解析一起编译的查询支持。子查询可以通过查找...__in=...
分隔,可以由Django编译,但是不可能使用OuterRef()
。如果它是在没有OuterRef()
的情况下编写的,那么它是一个如此复杂的非最佳嵌套SQL,时间复杂度可能是A
表的大小为O(n 2 ) (或所有)数据库后端。未经测试。