为包含相同多对多关系子集的对象过滤 django
Filter django for objects containing subsets of same many to many relationship
在我的数据库中,我有两个多对多字段(消息和后续)的用户对象,它们都包含与另一个对象主题相关的多对多字段。
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" 附加了 "messages" 但不包含附加到用户 "following" 对象的所有主题的所有 "users"。
现在我正在使用一个循环来完成这个:
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 helped me to understand and use
这是我的新模型和我的新查询:
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))
这应该可以使用子查询。
首先,确保 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 关系,其中外键可能更合适和更简单。您的关系越复杂,创建这种高级查询就越困难。具有大量数据库连接的查询可能会非常慢,因为与简单查询相比,它们可能必须处理大量数据。
例如,不使用 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)
基本上是一个 "through" 模型,如 django docs on model relationships 中有类似示例的解释。
在我的数据库中,我有两个多对多字段(消息和后续)的用户对象,它们都包含与另一个对象主题相关的多对多字段。
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" 附加了 "messages" 但不包含附加到用户 "following" 对象的所有主题的所有 "users"。
现在我正在使用一个循环来完成这个:
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 helped me to understand and use
这是我的新模型和我的新查询:
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))
这应该可以使用子查询。
首先,确保 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 关系,其中外键可能更合适和更简单。您的关系越复杂,创建这种高级查询就越困难。具有大量数据库连接的查询可能会非常慢,因为与简单查询相比,它们可能必须处理大量数据。
例如,不使用 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)
基本上是一个 "through" 模型,如 django docs on model relationships 中有类似示例的解释。