Python 如何强制Django在查询中使用左外部联接?

Python 如何强制Django在查询中使用左外部联接?,python,sql,django,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

我有两个模型:人和任务

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')
后来,我介绍了一个过滤器上是_删除然后没有任务的人消失了

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
qs = qs.annotate(
        child_count=Sum(
            Case(
                When(child__id=None, then=0),
                default=1,
                output_field=IntegerField())
            ))
有没有不使用原始SQL的方法来实现它

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。

有时django ORM决定使用内部联接,有时使用左外部联接。背后的逻辑是什么,我还没有找到。但我已经测试了一些案例,这些案例让我了解了背后的想法

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

任务1:计算每个父记录包含多少子记录 这应该起作用:

qs = qs.annotate(child_count_all=Count("child"))
查看
qs.query
-您可以看到使用了
左外连接
,什么是正确的

但如果我用SUM+CASE-WHEN进行计算:

qs = qs.annotate(
        child_count=Sum(Case(default=1), output_field=IntegerField())
        )
查看
qs.query
-您可以看到,这一次使用了
内部联接
,它将过滤掉所有不包含任何子记录的父记录,从而产生错误结果

解决方法如下:

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
qs = qs.annotate(
        child_count=Sum(
            Case(
                When(child__id=None, then=0),
                default=1,
                output_field=IntegerField())
            ))
这次
qs.query
使用
LEFT-OUTER-JOIN
显示,生成正确的结果

任务2:统计活动子记录包含的数量 检测到状态为“INA”的活动子记录。基于之前的解决方案,我尝试了以下方法:

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对象:

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相同,但仅统计已填入名称的记录 这项工作:

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())
            ))
结论
无法确定为什么有时使用内部连接,有时使用左连接,所以我的处理方法是通过检查qs.query来测试各种组合,直到找到正确的结果。另一种方法是使用
qs.raw/join/extra
和其他更本机和高级的django ORM/SQL组合。

谢谢您的回答。但它与我的第二个查询有相同的问题——没有任务的人消失了。这也是我使用Person.objects开始查询的原因。我想让所有人不管任务有多大谢谢Robert调查此事。我最终使用
qs.raw
来解决这个问题。SUM+CASE-WHEN可能是django的发展方向。但由于其不可预测的结果,这不是一个理想的解决方案。但很高兴知道。希望这将在未来的版本中修复。