通过反向外键注释Django查询集

时间:2017-10-01 21:33:59

标签: django django-orm

给出一组简单的模型如下:

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方法的默认值,但我找不到任何资源。

1 个答案:

答案 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)

这个简单的解决方案可以更容易修改和测试。

注意:一个查询的解决方案也存在,但似乎没用。原因:需要SubqueryOuterRef()。它们很棒,但是通常Count()聚合不会被与连接解析一起编译的查询支持。子查询可以通过查找...__in=...分隔,可以由Django编译,但是不可能使用OuterRef()。如果它是在没有OuterRef()的情况下编写的,那么它是一个如此复杂的非最佳嵌套SQL,时间复杂度可能是A表的大小为O(n 2 ) (或所有)数据库后端。未经测试。