Django QuerySet Union与only联用异常:为何仅values生效?
这是个很典型的Django ORM逻辑问题,核心和union()的实现机制以及QuerySet的方法执行顺序有关:
1. union()后的only()为什么会查询所有字段?
Django的union()方法直接映射SQL的UNION语句,而SQL的UNION要求所有参与联合的子查询必须返回完全一致的列集合(数量、类型、顺序)。
当你先执行union()再调用only()时,only()是作用在合并后的QuerySet上的。此时Django无法将字段限制的逻辑回推到之前的两个子查询中——因为union()已经完成了子查询的组合,后续的only()只能在Python内存层面过滤字段,而非在SQL层面优化查询。为了完成合并操作,Django会先让每个子查询返回模型的所有字段,合并后再在内存中筛选你指定的字段,这就导致Physician表的所有字段都被查询。
2. 为什么union()后调用values()可以正常生效?
values()和only()的逻辑不同:values()会直接在SQL层面生成只包含指定字段的查询,返回字典类型的QuerySet。当你在union()之后调用values()时,Django可以识别出需要将字段限制应用到整个联合查询中,生成的SQL会让每个子查询都只查询指定字段,再执行UNION,因此只会拉取你需要的字段。
3. 为什么每个子QuerySet单独调用only()可行?
当你在每个子QuerySet上先调用only(),相当于提前让每个子查询在SQL层面就只返回指定字段。此时两个子查询的列集合完全一致,满足SQL UNION的要求,Django可以直接生成只包含这些字段的UNION语句,自然只会查询你需要的字段,避免了重复拉取不必要的字段。
优化建议
如果你想避免重复定义字段列表,可以把字段名提取成一个变量,既保证代码简洁,又能实现字段限制:
PHYSICIAN_FIELDS = ('id', 'name_prefix', 'first_name', 'middle_name', 'last_name', 'name_suffix') query1 = doctors_models.Physician.objects.filter(appointments__member=self.context['member']).only(*PHYSICIAN_FIELDS) query2 = doctors_models.Physician.objects.filter(appointments__booked_by=self.context['member']).only(*PHYSICIAN_FIELDS) result = query1.union(query2)
内容的提问来源于stack exchange,提问作者SHIVAM JINDAL

