Django中如何用最少数据库查询实现父表与子表左连接?
问题
我希望通过最少的数据库查询实现左连接,获取所有State(州)记录及其关联的City(城市)记录(若无关联城市则显示空值)。尝试过.select_related、原生SQL查询(RawQuery)以及Q对象,但遇到以下问题:
- 使用
.select_related的代码cities_states = City.objects.all().select_related('state').order_by('state_id')无法获取无关联城市的State(如Illinois); - 使用原生SQL查询能得到所需数据,但无法区分State和City表中同名的字段(如id、name),不知如何单独获取City的id或name。
相关模型代码:
class State(models.Model): name = models.CharField(max_length=25) abbreviation = models.CharField(max_length=2) def __str__(self): return str(self.id) class City(models.Model): name = models.CharField(max_length=25) population = models.IntegerField() state = models.ForeignKey(State, related_name="cities", on_delete=models.CASCADE) def __str__(self): return str(self.id)
期望结果:所有州(包括无城市的州)与对应城市展开为行,无城市的州对应城市字段为空。
解决方案
1. 如何用最少的数据库查询获取所需数据?
要实现1次数据库查询完成左连接需求,直接使用原生SQL左连接是最优方案;如果接受2次高效查询(性能差异极小),也可以用Django ORM的prefetch_related结合Python层面的结果展开:
方法一:原生SQL左连接(1次查询)
从State表出发编写左连接SQL,确保包含所有州记录:
sql = ''' SELECT S.id, S.name, S.abbreviation, C.id AS city_id, C.name AS city_name, C.population, C.state_id FROM "state" S LEFT JOIN "city" C ON S.id = C.state_id ORDER BY S.id ASC ''' results = State.objects.raw(sql)
方法二:Django ORM + Python展开(2次查询)
用prefetch_related预加载所有关联城市,再在Python中遍历生成目标格式结果,更符合Django ORM使用习惯:
# 两次查询:一次获取所有州,一次获取所有关联城市,ORM自动完成关联 states = State.objects.all().prefetch_related('cities').order_by('id') # 展开结果为目标格式 expanded_results = [] for state in states: if state.cities.exists(): for city in state.cities: expanded_results.append({ 'state_id': state.id, 'state_name': state.name, 'abbreviation': state.abbreviation, 'city_id': city.id, 'city_name': city.name, 'population': city.population, 'city_state_id': city.state_id }) else: # 无关联城市的州填充空值 expanded_results.append({ 'state_id': state.id, 'state_name': state.name, 'abbreviation': state.abbreviation, 'city_id': None, 'city_name': None, 'population': None, 'city_state_id': None })
2. 使用原生查询时,如何区分State和City表中的同名字段?
在SQL查询语句中给同名字段设置别名,通过别名区分不同表的字段即可:
- 例如将City的
id命名为city_id,name命名为city_name; - 遍历
RawQuerySet时直接通过别名访问对应字段,避免字段覆盖。
示例代码:
sql = ''' SELECT S.id AS state_id, S.name AS state_name, S.abbreviation, C.id AS city_id, C.name AS city_name, C.population, C.state_id AS city_state_id FROM "state" S LEFT JOIN "city" C ON S.id = C.state_id ORDER BY S.id ASC ''' for item in State.objects.raw(sql): print(f"州ID: {item.state_id}, 州名: {item.state_name}") print(f"城市ID: {item.city_id}, 城市名: {item.city_name}\n")
内容的提问来源于stack exchange,提问作者jeetkunedo7
相关产品推荐
相关产品推荐

