用中介模型预防O(n)查询

时间:2012-11-29 02:17:41

标签: django django-models

我有以下型号:

class Artist(models.Model):
    name = models.CharField()

    def primary_group(self):
        return self.memberships.select_related('group').get(is_primary=True)

class Group(models.Model):
    name = models.CharField()
    members = models.ManyToManyField(Artist, through='Membership')

class Membership(models.Model):
    artist = models.ForeignKey(Artist, related_name='memberships')
    group = models.ForeignKey(Group)
    is_primary = models.BooleanField()

ArtistGroup通过中间模型Membership进行关联。艺术家只能有一个主要小组,通过is_primary标记,经过验证等等。

在我列出艺术家的模板中,我列出了除了主要组之外的基本艺术家信息,通过上述方法调用。然而,这是一个O(n)操作,我有大约160名艺术家这样做。 django-debug-toolbar提供的SQL如下:

SELECT ••• FROM "people_membership" 
           LEFT OUTER JOIN "people_group" ON ("people_membership"."group_id" = "people_group"."id") 
           WHERE ("people_membership"."artist_id" = xx AND "people_membership"."is_primary" = true )

让我补充一点,这对每个列出的艺术家都会发生,所以我得到了大约160个。

考虑到我称之为模型方法,O(n)是最好的吗?或者我还能做些什么来改善这一点(缺少非规范化primary_group)?这似乎是存储在中间模型中的任何信息的问题,我想从源或目标中调用。

4 个答案:

答案 0 :(得分:6)

您可以通过两个查询轻松完成此操作,尽管任何仇恨者都会说,但这些查询根本不重要:

artists = list(Artist.objects.all())
primary_memberships = {m.artist_id: m for m in Group.objects.filter(is_primary=True, membership__artist__in=artists).extra(select={'artist_id': '%s.artist_id' % (Membership._meta.db_table,)})}
for artist in artists:
    artist.primary_membership = primary_memberships.get(artist.id)

(额外条款可能不正确,但你明白了)

除此之外,我还会将主要功能更改为:

if hasattr(self, '_primary_membership_cache'):
    return self._primary_membership_cache

然后,如果您附加信息,请将其绑定到该变量,并使用相同的函数调用。

(我们在DISQUS各处遵循这种模式进行各种连接/奇数查询)

答案 1 :(得分:4)

我会像David Cramer所说的那样做,而不是额外的:

primary_memberships = {m.artist_id: m.group for m in Membership.objects.filter(group__isprimary=True, artist__in=artists).select_related('group')}
for artist in artists:
    artists.primary_membership = primary_memberships.get(artist.id)

奖励积分使其成为会员经理的一种方法,以便您轻松将其应用于任何艺术家名单!

答案 2 :(得分:1)

如何在membershipartist_idis_primary)上添加两列索引? If you've already upgraded to 1.5b1你可以在你的模型中做到这一点,但如果你没有,那么没有什么能阻止你在后端做这件事。这应该将成员资格查找减少到恒定时间。如果您的数据库支持它,您可以将其设为partial index,但只有160位艺术家,这似乎并非一切。

答案 3 :(得分:0)

您是否尝试使用成员资格而不是艺术家来启动查询?

class Artist(models.Model):
    ...
    def primary_group(self):
        return Membership.objects.filter(artist=self).get(is_primary=True).group