用于迭代嵌套结果的Django Queryset Prefetch Otimization

时间:2016-04-01 13:09:53

标签: django django-models django-queryset django-orm

我正在寻找一种通过提高数据库访问性能来优化Django中的查询集结果处理的方法,考虑到我想获取嵌套关系。

例如,我已经建立了这个结构:

class Movie(models.Model):
    name = models.CharField(max_length=50)

class Ticket(models.Model):
    code = models.CharField(max_length=255, blank=True, unique=True)
    movie = models.ForeignKey(Movie, related_name='tickets')

class Buyer(models.Model):
    name = models.CharField(max_length=50)

class Purchase(models.Model):
    tickets = models.ManyToManyField(Ticket, related_name='purchases')
    buyer = models.ForeignKey(Buyer, related_name='purchases')

我们说我有一个Movie QuerySet:

movies = Movie.objects.all().prefetch_related('tickets__purchases__buyer')

如果我想在此qs中检索每部电影中的所有买家,我可以这样做:

for movie in movies:
    buyers = Buyer.objects.filter(purchases__tickets__in=movie.tickets.all()).distinct()

但是在这种方法中,它会再次针对每个迭代的电影访问数据库。要解决此问题,请执行以下操作:

def get_movie_buyers(movie):
    buyers = set()
    for ticket in movie.tickets.all():
        for purchase in ticket.purchases.all():
            if purchase.buyer:
                buyers.add(purchase.buyer)
    return buyers
for movie in movies:
    buyers = get_movie_buyers(movie)
    # do something with the buyers

这样它只会打到数据库一次,因为之前我使用过prefetch_related,但我不认为这是一个很好的方法,因为我必须迭代许多嵌套循环,这将增加内存过载

我认为这是一个更好的方法,但我仍然没有弄清楚“正确的”#39;我希望有人可以指导我完成它。

更新

提到alasdair,使用Prefetch对象,但我已经尝试使用以下内容:

movies = Movie.objects.prefetch_related(
    Prefetch(lookup='tickets__purchases__buyer',
             to_attr='buyers')
).all()
for movie in movies:
    print movie.buyers

这给了我以下错误: 'Movie' object has no attribute 'buyers'

1 个答案:

答案 0 :(得分:0)

之所以看起来太困难,是因为“购买”和“票证”之间的“多对多”关系。

此关系允许同一票可以多次购买。但这在实际数据中并非如此,因为一张票只能购买一次。 如果删除此ManyToMany字段并在要购买的票证中添加ForeignKey字段,则可以简化查询

class Ticket(models.Model):
    code = models.CharField(max_length=255, blank=True, unique=True)
    movie = models.ForeignKey(Movie, related_name='tickets')
    purchase = models.ForeignKey(Purchase, null=True, blank=True)

然后可以如下简化查询。

movies = Movie.objects.all().prefetch_related('tickets__purchase__buyer')
for movie in movies:
    print(set(ticket.purchase.buyer for ticket in movie.tickets if ticket.purchase))

确保在创建“购买”时会增加额外的复杂性,因为您需要使用purchase_id更新票证对象。

您需要根据两个操作的频率来调用将复杂性保持在何处