首页 > 解决方案 > 为包含相同多对多关系子集的对象过滤 django

问题描述

在我的数据库中,我的用户对象上有两个多对多字段(消息和关注),它们都包含与另一个对象主题相关的多对多字段。

class User():
    messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
    following = ForeignKey('Following', related_name='users', blank=True, null=True) 

class Message():
    date = DateField(blank=True, null=True)
    content = TextField(blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)

class Following():
    name = CharField(max_length=255, blank=True, null=True)
    description = CharField(max_length=255, blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='following', blank=True, null=True)

class Topic():
    name = CharField(max_length=255, blank=True, null=True)
    source = CharField(max_length=255, blank=True, null=True)

我想过滤所有附加了“消息”的“用户”,这些“消息”不包含附加到用户“关注”对象的所有主题。

现在我正在使用一个循环来完成这个:

users = set()
for user in User.objects.filter(messages__isnull=False, following__isnull=False).iterator():
    if not set(user.following.values_list('topics', flat=True))
               ).issubset(set(user.messages.values_list('topics', flat=True)):
       users.add(user.pk)

有没有办法用一个查询来完成同样的事情?

- - 编辑 - -

我所拥有的是:

User.objects.filter(following__isnull=False
).annotate(following_count=Count('following__topics', distinct=True)
).filter(following__topics__exact=F('message__topics')
).annotate(missing_topics=ExpressionWrapper(
    F('following_count') - Count('message__topics', distinct=True),
                                 IntegerField())
).filter(missing_topics__gt=0)

如果有更好的方法可以做到这一点,或者有理由我绝对不应该这样做,它们是什么?

- - 编辑 - -

这个问题帮助我理解和使用了 Håken Lid 的回答

这是我的新模型和新查询:

class User():
    messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
    following = ManyToManyField('Topic', through='Following', related_name='users', blank=True, null=True) 

class Message():
    date = DateField(blank=True, null=True)
    content = TextField(blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)

class Following():
    name = CharField(max_length=255, blank=True, null=True)
    description = CharField(max_length=255, blank=True, null=True)
    user = ForeignKey('User', related_name='following', blank=True, null=True)
    topic = ForeignKey('Topic', related_name='following', blank=True, null=True)

class Topic():
    name = CharField(max_length=255, blank=True, null=True)
    source = CharField(max_length=255, blank=True, null=True)



User.objects.filter(~Q(messages__topics__in=F('following'))
).values('id').annotate(missing_topics=Count('following__topics', distinct=True))

标签: djangodjango-querysetpython-3.5

解决方案


这应该可以使用子查询来实现。

首先,确保Following.topics使用与 不同的相关名称Messages.topics

class Following(models.Model):
    topics = ManyToManyField('Topic', related_name='following')

然后应该可以创建一个子查询。像这样的东西:

from django.db.models import OuterRef, Subquery
user_following_topic = Topic.objects.filter(following__users=OuterRef('pk'))
User.objects.exclude(messages__topics__in=Subquery(user_following_topics.values('pk')))

这可能行不通,并且给您的预期输出与所写的完全相同,但我认为该原则也应该适用于您的情况。

另一方面,我不太了解您的数据库结构。您似乎使用 m2m 关系,其中外键可能更合适和更简单。您的关系越复杂,创建这种高级查询就越困难。与简单查询相比,具有大量数据库连接的查询可能会非常慢,因为它们可能必须处理大量数据。

例如,而不是使用 m2m 现实,Following对我来说更有意义,如下所示:

class Following():
    topic = ForeignKey('Topic', on_delete=models.CASCADE)
    user = ForeignKey('User', on_delete=models.CASCADE)
    client = models.CharField(max_length=255, blank=True, null=True)
    duration = fields.DateRangeField(blank=False, null=False)

所以基本上是一个“通过”模型,正如django 文档中关于模型关系的解释,其中有一个类似的例子。


推荐阅读