新手求助:SQL关联查询转Django ORM时的条件处理问题
别担心,刚接触Django Models遇到这种聚合关联的问题很正常,我来一步步帮你把目标SQL转成正确的ORM代码。
首先理清楚你的核心需求:左连接两张表,按tb1.id分组,计算两个聚合值(是否所有关联的tb2.version都相同,以及最大的version),同时保留tb1的所有字段。
先纠正一个可能的模型问题
看你的Tb2模型,tb2_id被设为ForeignKey且是主键,这意味着一个Tb1只能对应一个Tb2,那SQL里的count(distinct p.version)永远只会是0(无关联数据)或1(有一条关联数据),is_profile_same永远为True,这大概率和你实际业务场景不符。如果你的业务是一个Tb1对应多个Tb2,建议调整Tb2的模型定义,给它自己的主键:
class Tb2(models.Model): id = models.BigAutoField(primary_key=True) # 新增独立主键 tb2_id = models.ForeignKey('Tb1', models.DO_NOTHING, related_name='tb2_entries') # 自定义关联名方便调用 profile = models.CharField(max_length=15) version = models.CharField(max_length=15)
正确的ORM查询代码
不管模型是一对一还是一对多,我们都可以用annotate结合聚合函数来实现你的SQL逻辑。首先需要导入Django的几个核心函数:
from django.db.models import Count, Max, Case, When, BooleanField
然后编写查询语句:
# 如果你调整了Tb2模型用了related_name='tb2_entries',就把下面的'tb2'改成'tb2_entries' queryset = Tb1.objects.filter(id__in=['10', '9']).annotate( # 统计关联的tb2中不同version的去重数量 distinct_version_count=Count('tb2__version', distinct=True), # 取关联tb2中的最大version ver=Max('tb2__version'), # 实现SQL里的IF逻辑:不同版本数<=1则返回True,否则False is_profile_same=Case( When(distinct_version_count__lte=1, then=True), default=False, output_field=BooleanField() ) ) # 方式1:用values获取指定字段(tb1字段+聚合字段) result = queryset.values('id', 'job_id', 'is_profile_same', 'ver') # 方式2:直接遍历模型对象,每个对象会附带新增的聚合字段 for obj in queryset: print(obj.id, obj.job_id, obj.is_profile_same, obj.ver)
为什么你的之前代码会报错?
你之前用select_related('Tb2'),这个方法是用来提前加载一对一/多对一的关联对象,但你的需求是对关联表的字段做聚合计算,这时候应该用annotate来生成聚合字段,而不是select_related。另外你之前Count('id')统计的是Tb1自身的id数量,完全不是你需要的关联表version的去重计数,这也是导致FieldError的核心原因。
验证生成的SQL
这个ORM查询会生成和你目标SQL几乎一致的语句:左连接tb1和tb2,过滤id在指定列表,按tb1.id分组,计算去重version数、最大version,以及用CASE WHEN实现你要的IF逻辑。
内容的提问来源于stack exchange,提问作者RNA

