You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django ORM中无外键实现LEFT OUTER JOIN查询

Django QuerySet 构建问题:生成指定LEFT JOIN SQL

现有模型代码

class User(Model):
    ### 
    user fields
    ###

class CaseModel(Model):
    id = # django build in id field
    owner = models.ForeignKey('User')
    ### 
    other fields
    ###


class DraftMessageModel(Model):
    entity_id = models.IntegerField(null=True, blank=True) # CaseModel.id is linked to this
    ### 
    other fields
    ###

目标SQL

SELECT 
  C.id, 
  C.some_field, 
  D.another_field, 
  U.username,
  < some extra fields > 
FROM 
  CaseModel AS C 
  LEFT OUTER JOIN User AS U ON (C.owner_id = U.id) 
  LEFT OUTER JOIN DraftMessageModel AS D ON (C.id = D.entity_id) 
WHERE 
  C.owner_id = 100 
  AND < some extra condition >

已尝试的方法及问题

方法1:annotate + FilteredRelation 报错

代码:

queryset = CaseModel.objects.select_related('owner').filter(owner_id=100)

queryset = queryset.annotate(
    draft_message=FilteredRelation(
        'draftmessagemodel',
        condition=Q(draftmessagemodel__entity_id=F('id'))
    )
)

错误提示:字段解析错误,无法将'draftmessagemodel'解析为字段

方法2:extra 生成SQL不符合预期

代码:

queryset = CaseModel.objects.select_related('owner').filter(owner_id=100)

queryset = queryset.extra(
    select={
        'draftmessagemodel__id': 'draftmessagemodel.id',
        'draftmessagemodel__text': 'draftmessagemodel.text',
    },
    tables=['draftmessagemodel'],
    where=[
        'casemodel.id = draftmessagemodel.entity_id'
    ]
)

问题:表关联处出现多余逗号,且未实现LEFT OUTER JOIN(实际生成INNER JOIN)

解决方案

方案1:修改模型添加外键关联(推荐)

如果能修改DraftMessageModel,把entity_id改成关联CaseModel的外键,ORM就能自动处理关联逻辑:

class DraftMessageModel(Model):
    # 替换原有的entity_id字段
    entity = models.ForeignKey(
        'CaseModel', 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='draft_messages'
    )
    ### 
    other fields
    ###

之后直接用ORM的关联查询即可生成目标LEFT JOIN:

from django.db.models import F

# 构造QuerySet,指定需要的字段
queryset = CaseModel.objects.select_related('owner').filter(owner_id=100).annotate(
    draft_another_field=F('draft_messages__another_field')
).values(
    'id', 'some_field', 'draft_another_field', 'owner__username'
)

方案2:不修改模型,用Subquery实现关联查询

如果无法修改模型,用子查询关联DraftMessageModel的对应字段:

from django.db.models import Subquery, OuterRef

# 定义子查询,匹配当前CaseModel的id
draft_subquery = DraftMessageModel.objects.filter(
    entity_id=OuterRef('id')
).values('another_field')[:1]

# 主查询中注入子查询结果
queryset = CaseModel.objects.select_related('owner').filter(owner_id=100).annotate(
    draft_another_field=Subquery(draft_subquery)
).values(
    'id', 'some_field', 'draft_another_field', 'owner__username'
)

这种方式会生成类似LEFT JOIN的效果,保证即使没有对应Draft记录,CaseModel数据也会被保留。

方案3:用RawSQL手动构造左连接逻辑

如果需要更精细的SQL控制,可以直接用RawSQL指定关联逻辑:

from django.db.models import RawSQL, F

queryset = CaseModel.objects.select_related('owner').filter(owner_id=100).annotate(
    draft_another_field=RawSQL(
        'SELECT D.another_field FROM draftmessagemodel D WHERE D.entity_id = %s',
        (F('id'),)
    )
)

错误原因说明

  • 方法1失败:FilteredRelation依赖ORM层面的模型关联(比如外键的反向关系),但当前CaseModel和DraftMessageModel没有定义任何关联,所以'draftmessagemodel'这个关系名不存在,导致解析错误。
  • 方法2失败:extra的tables参数默认用INNER JOIN添加表,加上手动指定的where条件,最终生成的是INNER JOIN而非LEFT JOIN,不符合需求。

内容的提问来源于stack exchange,提问作者Kholdarbekov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 02:15:12