在Django ORM中实现类Full Outer Join查询及Raw Query报错问题
解决方案
一、Django ORM 实现方式
你的需求本质是获取所有Person,同时关联指定Expense对应的Debitor记录(无关联则为null),用ORM完全可以实现,无需原生SQL:
方法1:通过注解(Annotate)关联数据
从Person模型出发,用子查询将指定Expense的Debitor信息附加到每个Person实例上:
from django.db.models import OuterRef, Subquery target_expense_id = 6 # 子查询:获取当前Person对应目标Expense的Debitor记录 debitor_subquery = Debitor.objects.filter( person=OuterRef('pk'), expense_id=target_expense_id ).values('id', 'amount', 'expense_id') # 查询所有Person,附加Debitor相关字段 results = Person.objects.annotate( debitor_id=Subquery(debitor_subquery.values('id')[:1]), debitor_amount=Subquery(debitor_subquery.values('amount')[:1]), debitor_expense_id=Subquery(debitor_subquery.values('expense_id')[:1]) ).all()
查询结果中:
- 有对应Debitor记录的Person,
debitor_id/debitor_amount/debitor_expense_id会有值 - 无对应记录的Person,这三个字段为
None
方法2:通过预取(Prefetch)关联数据
如果需要直接拿到Debitor实例对象,可以用Prefetch过滤指定Expense的关联:
from django.db.models import Prefetch target_expense = Expense.objects.get(id=6) # 预取仅当前Expense的Debitor记录,存到自定义属性中 prefetch = Prefetch( 'debitor_set', queryset=Debitor.objects.filter(expense=target_expense), to_attr='related_debitor' ) persons = Person.objects.prefetch_related(prefetch).all() # 遍历处理结果 for person in persons: # 取关联的Debitor(无则为None) related_debitor = person.related_debitor[0] if person.related_debitor else None
二、原生SQL报错的原因及修复
你用Debitor.objects.raw()报错有两个核心原因:
Debitor模型的id是主键,不允许为NULL,但你的SQL中无对应Debitor记录的行,debitor_id会是NULL,违反模型约束Debitor.person是ForeignKey字段,Django期望映射为Person实例,但SQL返回的是id数值,无法直接转换
如果一定要用原生SQL,不要绑定到Debitor模型,直接用游标执行并返回字典结果:
from django.db import connection target_expense_id = 6 sql = """ SELECT debitor_id as id, amount, src_person.id as person_id, src_person.name as person_name, expense_id as expense FROM src_person LEFT JOIN (SELECT src_debitor.id as debitor_id, src_debitor.amount as amount, src_expense.id as expense_id, src_debitor.person_id as person_id FROM src_debitor INNER JOIN src_expense ON src_debitor.expense_id = src_expense.id and src_expense.id = %s) as sol ON src_person.id = sol.person_id; """ with connection.cursor() as cursor: cursor.execute(sql, [target_expense_id]) # 获取列名并转换为字典列表 columns = [col[0] for col in cursor.description] results = [dict(zip(columns, row)) for row in cursor.fetchall()]
返回的结果是字典格式,包含所有需要的字段,无关联记录的字段会为NULL。
三、关于“类Full Outer Join”的说明
你的需求其实不需要Full Outer Join——Full Outer Join会同时保留两边表的所有记录,但你只需要所有Person + 对应指定Expense的Debitor记录,这是典型的LEFT JOIN场景,用Person左连过滤后的Debitor即可实现,无需复杂的全外连接逻辑。
内容的提问来源于stack exchange,提问作者Compizfox
相关产品推荐
相关产品推荐

