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的发展方向。但由于其不可预测的结果,这不是一个理想的解决方案。但很高兴知道。希望这将在未来的版本中修复。