如何强制Django在查询中使用LEFT OUTER JOIN?

时间:2014-03-13 10:24:27

标签: python sql django

我有两个模型:人和任务。

class Person(models.Model):
    display_name = models.CharField()
    ...

class Task(models.Model):
    person = models.ForeignKey(Person)
    is_deleted = models.BooleanField()
    ...

我想获得所有人的列表以及任务量(包括0)。

最初,我写下面的查询,它运作得很好:

Person.objects.values('person_id', 'display_name').annotate(crt_task_amt=Count('task__id')).order_by('-crt_task_amt', 'display_name')

后来,我在is_deleted上引入了一个过滤器。 然后没有任务的人消失

Person.objects.filter(task__is_deleted=False).values('person_id', 'display_name').annotate(crt_task_amt=Count('task__id')).order_by('-crt_task_amt', 'display_name')

我正在寻找类似的东西:

SELECT p.id, p.display_name, count(t.id) FROM dashboard_person p LEFT OUTER JOIN dashboard_task t ON (p.person_id=t.person_id AND t.is_deleted=0) GROUP BY t.person_id

有没有办法在不使用原始SQL的情况下实现它?

2 个答案:

答案 0 :(得分:3)

有时django ORM决定使用INNER JOIN,有时候使用LEFT OUTER JOIN。什么是背后的逻辑,我还没有找到。但我已经测试了一些让我了解背后的案例。

起始案例(我使用的是django 1.8.1):

await

任务1:为每个父记录计数包含

的子记录数

这应该有效:

class Parent(...)
    ...

class Child(...):
    parent = ForeignKey(Parent)
    status = CharField()
    name   = CharField()
    ...

qs = Parent.object.all()

展望qs = qs.annotate(child_count_all=Count("child")) - 您可以看到qs.query被使用,正确

但如果我使用SUM + CASE-WHEN:

LEFT OUTER JOIN

调查qs = qs.annotate( child_count=Sum(Case(default=1), output_field=IntegerField()) ) - 您可以看到此次使用qs.query,将过滤掉所有不包含任何子记录的父记录,产生错误结果

此解决方法类似于:

INNER JOIN

这一次qs = qs.annotate( child_count=Sum( Case( When(child__id=None, then=0), default=1, output_field=IntegerField()) )) 显示使用qs.query生成正确的结果。

任务2:计算包含

的活动子记录数

检测到状态为<>'INA'的活动子记录。根据之前的解决方案,我尝试了以下内容:

LEFT OUTER JOIN

但同样,qs = qs.annotate( child_count=Sum( Case( When(child__id=None, then=0), When(child__status='INA', then=0), default=1, output_field=IntegerField()) )) 显示正在使用qs.query,从而产生错误结果(对于我的情况)。

变通方法/解决方案是使用两个或多个Q对象:

INNER JOIN

同样,qs = qs.annotate( child_count=Sum( Case( When(Q(child__id=None) | Q(child__status="INA"), then=0), default=1, output_field=IntegerField()) )) 使用qs.query,产生正确的结果。

任务3:与2相同,但只计算名称已填充的记录

这有效:

LEFT OUTER JOIN

结论

无法确定为什么有时会使用内部连接,有时会使用左连接,所以我如何处理它的方法是通过检查qs.query来测试各种组合,直到找到正确的结果。其他方法是使用qs = qs.annotate( child_with_name_count=Sum( Case( When(Q(child__id=None) | Q(child__status="INA"), then=0), When(child__name__isnull=False, then=1), default=0, output_field=IntegerField()) )) 和其他更多本机和高级django ORM / SQL组合。

答案 1 :(得分:0)

q = Task.objects.filter(is_deleted=False).values('person__id').annotate(crt_task_amt=Count('id')).order_by('-crt_task_amt', 'person__display_name')

q[0].person_id  # gives person_id
q[0].display_name #gives person name
q[0].crt_task_amt # gives count of task of first person

<强>更新

希望这有效。

Task.objects.filter(is_deleted=False, person__isnull = True).values('person__id').annotate(crt_task_amt=Count('id')).order_by('-crt_task_amt', 'person__display_name')

这可以使用连接轻松完成,但您需要使用一点原始SQL。