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

在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()报错有两个核心原因:

  1. Debitor模型的id是主键,不允许为NULL,但你的SQL中无对应Debitor记录的行,debitor_id会是NULL,违反模型约束
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 21:30:37