Django预取和选择相关

7 浏览
0 Comments

Django预取和选择相关

我在理解Django ORM中的prefetch_relatedselect_related方面遇到了困难。 我有以下模型:

class City(models.Model):
    name = models.CharField(max_length=35)
    state = models.ForeignKey('states.State', on_delete=models.CASCADE)
class Street(models.Model):
    name = models.CharField(max_length=35)
    building = models.ForeignKey('buildings.Building', on_delete=models.CASCADE)
    city = models.ForeignKey('cities.City', on_delete=models.CASCADE)

现在我的views.py:

cities = City.objects.all()
streets = Street.objects.all()
for city in cities: 
    has_bank = streets.filter(building_id=1, city=city)
    if has_bank:
        city.has_bank = 1
    has_cinema = streets.filter(building_id=2, city=city)
    if has_cinema:
        city.has_cinema = 1
    has_church = streets.filter(building_id=3, city=city)
    if has_church:
        city.has_church = 1

但现在,在每次for循环迭代时,它都会命中3次数据库。我正在尝试改进时间复杂度-现在为3N + 2,其中N是城市数量,但我无法理解select_related和prefetch_related。

您能给我一个例子,我如何改进这个函数,使其在for循环中不会三次击中数据库吗?

admin 更改状态以发布 2023年5月21日
0
0 Comments

选择相关内容。让我解释一下。我添加了一些虚拟数据以便说明。

class City(models.Model):
    name = models.CharField(max_length=35)
    state = models.ForeignKey('states.State', on_delete=models.CASCADE)
class Street(models.Model):
    name = models.CharField(max_length=35)
    building = models.ForeignKey('buildings.Building', on_delete=models.CASCADE)
    city = models.ForeignKey('cities.City', on_delete=models.CASCADE)

你的城市表应该是这样的。

id    name         state
1   Cityname1      state1  
2   Cityname2        2

你的街道表应该是这样的。

id  name   city ..
1   st 1    1
2   stno.2  1
3   st no3  2

如果你的ORM查询是这样的。

street = Street.objects.select_related('city')

这个查询将两个表组合成单个表。这意味着所有城市ID外键将连接到每个街道ID,以创建以下新表。它将返回三条记录,因为在这种情况下,我们使用选择相关的城市,并且主表是Street,所以它将返回3条记录。在所有情况下,主表都将首先在Dajngo中返回。

 id   name   city ..  city.id  city.name  city.state
  1   st 1    1         1      Cityname1   state1
  2   stno.2  1         1      Cityname1   state1
  3   st no3  2         2      Cityname2    2

0
0 Comments

根据您的具体情况,我建议使用注释而不是预取:

from django.db.models import Count, Q
cities = City.objects
.annotate(bank_count=Count("street", filter=Q(street__building_id=1)))
.annotate(cinema_count=Count("street", filter=Q(street__building_id=2)))
.annotate(church_count=Count("street", filter=Q(street__building_id=3)))

现在你可以直接使用bank_countcinema_countchurch_count属性:

for city in cities: 
   print(city.bank_count)
   print(city.cinema_count)
   print(city.church_count)

如果您想要使用prefetch_related,您需要使用Prefetch对象。这可以让您过滤预获取的对象:

City.objects.prefect_related(
    Prefetch("street_set", queryset=Street.objects.filter(building_id=1), to_attr='has_bank'),
    Prefetch("street_set", queryset=Street.objects.filter(building_id=2), to_attr='has_cinema'),
    Prefetch("street_set", queryset=Street.objects.filter(building_id=3), to_attr='has_church')
)

请注意to_attr参数,这可以帮助您将同一模型的对象根据不同的过滤器预取到不同的属性中。所以现在你可以这么做:

for city in cities: 
   print(city.has_bank)
   print(city.has_cinema)
   print(city.has_church)

0