代码之家  ›  专栏  ›  技术社区  ›  B.Adler

为包含相同多对多关系子集的对象筛选django

  •  0
  • B.Adler  · 技术社区  · 7 年前

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

    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)
    

    我要筛选所有附加了“消息”的“用户”,这些消息不包含附加到用户上“following”对象的所有主题。

    现在我正在使用一个循环来实现这一点:

    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)
    

    如果有更好的方法来做这件事,或者有理由我绝对不应该这样做,他们是什么?

    This question 帮助我理解和使用 HÃ¥ken Lid's answer

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

    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))
    
    1 回复  |  直到 7 年前
        1
  •  1
  •   Håken Lid    7 年前

    这应该可以使用子查询。

    Following.topics 使用不同的相关名称 Messages.topics .

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

    那么应该可以创建一个 subquery . 像这样:

    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关系,其中外键可能更合适、更简单。关系越复杂,创建这种高级查询就越困难。与简单的查询相比,具有大量数据库连接的查询可能会非常慢,因为它们可能需要处理大量的数据。

    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 docs on model relationships 这里有一个类似的例子。