Django 预取和选择相关

Posted

技术标签:

【中文标题】Django 预取和选择相关【英文标题】:Django prefetch and select related 【发布时间】:2020-05-09 23:18:30 【问题描述】:

我很难理解 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 循环中访问数据库 3 次?

【问题讨论】:

这能回答你的问题吗? What's the difference between select_related and prefetch_related in Django ORM? 我知道有什么区别,但我无法让它与 streets.filter() 一起工作我认为它会“查询”QuerySet 但它不会。 它会为你预取关系数据。 @AshrafulIslam 但是我如何将它应用于.filter()。我真的很感激一个简单的例子。 例如streets = Street.objects.all().select_related('building') 【参考方案1】:

选择相关的。让我稍微解释一下。我添加了虚拟数据来解释。

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

你的 Street 表应该是。

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

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

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

此查询将两个表合并为一个。这意味着所有城市 id 外键将加入每个街道 id 以创建新表,如下所示。它将返回三个记录,因为我们使用的是选择相关城市和主表在这种情况下是街道,因此它将返回 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

【讨论】:

【参考方案2】:

在您的具体情况下,我想最好使用annotation 而不是预取:

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)

【讨论】:

我刚刚看到,我错过了 for 循环查询中的一个参数 - 已编辑帖子。使用注释还是更好吗?我还是想了解prefetch_relatedselect_related @popcorn 是的,我会说它更好。注释将准确给出您在更新问题中所需要的内容。好吧,prefetch 和 select_related 也是。但是 select related 在这种情况下并不适用,因为它只能预测单个对象而不是多个对象。至于预取,这将适用于您的情况,但每个 prefetch_related 调用都会进行额外的数据库查询。因此,在您的情况下,您需要预取 3 个查询集,它将为您提供 3 个额外的数据库调用。至于注释,它只是一个查询。它会为您提供所需的数据。 这个链接可能会有所帮助docs.djangoproject.com/en/3.0/ref/models/querysets/… 和 docs.djangoproject.com/en/3.0/ref/models/querysets/… 添加了 prefetch_related 示例以使其清晰。

以上是关于Django 预取和选择相关的主要内容,如果未能解决你的问题,请参考以下文章

核心数据预取和 KVO 合规性

Tensorflow 数据集预取和缓存选项的正确用途是啥?

预取和缓存动态创建的 jquery Mobile+PhoneGap 页面

通过预取提高性能并选择相关

成果分享:边缘智能视频预取和缓存机制

如何在 Vue-CLI 3 中将预取和预加载资源插入到我的自定义 HTML 文件中?