简体   繁体   English

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

[英]How to force Django to use LEFT OUTER JOIN in query?

I have two models: Person and Task. 我有两个模型:人和任务。

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

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

I want to get a list of ALL people along with amount of tasks (including 0). 我想获得所有人的列表以及任务量(包括0)。

Initially, I wrote below query and it worked pretty well: 最初,我写下面的查询,它工作得很好:

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

Later, I introduced a filter on is_deleted. 后来,我在is_deleted上引入了一个过滤器。 Then people with no tasks disappeared : 然后没有任务的人消失了

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')

I'm looking for something like: 我正在寻找类似的东西:

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

Is there any way to achieve it without using raw SQL? 有没有办法在不使用原始SQL的情况下实现它?

Sometimes django ORM decides to use INNER JOIN and sometimes LEFT OUTER JOIN. 有时django ORM决定使用INNER JOIN,有时候使用LEFT OUTER JOIN。 What is the logic behind, I haven't found yet. 什么是背后的逻辑,我还没有找到。 But I have tested some cases which get me idea behind. 但我已经测试了一些让我了解背后的案例。

Starting case (I am using django 1.8.1): 起始情况(我使用的是django 1.8.1):

class Parent(...)
    ...

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

qs = Parent.object.all()

Task 1: for each parent record count how many child records contains 任务1:为每个父记录计数包含多少个子记录

This should work: 这应该工作:

qs = qs.annotate(child_count_all=Count("child"))

Looking into qs.query - you can see that LEFT OUTER JOIN is used, what is correct . 查看qs.query - 您可以看到使用了LEFT OUTER JOIN ,这是正确的

but if I do it with SUM + CASE-WHEN: 但如果我用SUM + CASE-WHEN:

qs = qs.annotate(
        child_count=Sum(Case(default=1), output_field=IntegerField())
        )

Looking into qs.query - you can see that this time INNER JOIN is used, what will filter out all parent records which don't contain any child records, producing wrong results . 查看qs.query - 您可以看到这次使用INNER JOIN ,将过滤掉所有不包含任何子记录的父记录,从而产生错误的结果

The workaround for this is something like: 解决方法是这样的:

qs = qs.annotate(
        child_count=Sum(
            Case(
                When(child__id=None, then=0),
                default=1,
                output_field=IntegerField())
            ))

This time qs.query showed using LEFT OUTER JOIN producing correct results. 这次qs.query显示使用LEFT OUTER JOIN产生正确的结果。

Task 2: count how many active child records contains 任务2:计算包含的活动子记录数

Active child records are detected with status<>'INA'. 检测到状态为<>'INA'的活动子记录。 Based on previous solution I tried following: 基于以前的解决方案,我试过以下:

qs = qs.annotate(
        child_count=Sum(
            Case(
                When(child__id=None, then=0),
                When(child__status='INA', then=0),
                default=1,
                output_field=IntegerField())
            ))

but again, the qs.query shows that INNER JOIN is being used, thus producing wrong results (for my case). 但同样, qs.query显示正在使用INNER JOIN ,从而产生错误的结果 (对于我的情况)。

The workaround/solution is using two or-ed Q objects: 解决方法/解决方案是使用两个或多个Q对象:

qs = qs.annotate(
        child_count=Sum(
            Case(
                When(Q(child__id=None) | Q(child__status="INA"), then=0),
                default=1,
                output_field=IntegerField())
            ))

Again, qs.query used LEFT OUTER JOIN , yielding correct results. 同样, qs.query使用了LEFT OUTER JOIN ,产生了正确的结果。

Task 3: same as 2 but count only records which have name filled 任务3:与2相同,但仅计算已填写名称的记录

This works: 这有效:

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())
            ))

Conclusion 结论

Can not tell for sure why is sometimes used inner and sometimes left join, so my way how to deal with it was to test various combinations by inspecting qs.query until I found proper result. 无法确定为什么有时使用内部,有时左边连接,所以我如何处理它的方法是通过检查qs.query测试各种组合,直到我找到正确的结果。 Other way is by using qs.raw/join/extra and other more native and advanced django ORM/SQL combinations. 其他方法是使用qs.raw/join/extra和其他更多本机和高级django ORM / 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

UPDATE: 更新:

Hope this works. 希望这有效。

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')

This can be done easily using joins, but you need to use little bit of raw SQL for that. 这可以使用连接轻松完成,但您需要使用一点原始SQL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM