5

我有以下型号:

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

4 回答 4

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 处处都遵循这种模式)

于 2012-11-29T02:21:39.380 回答
4

我会像大卫克莱默所说的那样做,但不是额外的:

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)

对于奖励积分,请将此方法作为会员经理的方法,以便您可以轻松地将其应用于任何艺术家列表!

于 2012-11-29T02:40:04.170 回答
1

membership在( artist_id, )上放置一个两列索引is_primary怎么样? 如果您已经升级到 1.5b1,您可以在您的模型中执行此操作,但如果您还没有在后端执行此操作,则没有什么能阻止您这样做。这应该将成员资格查找减少到恒定时间。如果您的数据库支持它,您可以将其设为 partial index,但只有 160 位艺术家,这似乎并不那么必要。

于 2012-11-29T02:42:20.523 回答
0

您是否尝试过从会员而不是艺术家开始查询?

class Artist(models.Model):
    ...
    def primary_group(self):
        return Membership.objects.filter(artist=self).get(is_primary=True).group
于 2012-11-29T02:24:06.103 回答