Django过滤同一外键对象的两个字段

Posted

技术标签:

【中文标题】Django过滤同一外键对象的两个字段【英文标题】:Django filter on two fields of the same foreign key object 【发布时间】:2014-09-29 03:04:00 【问题描述】:

我有一个类似这样的数据库架构:

class User(models.Model):
    … (Some fields irrelevant for this query)

class UserNotifiy(models.Model):
    user = models.ForeignKey(User)
    target = models.ForeignKey(<Some other Model>)
    notification_level = models.SmallPositivIntegerField(choices=(1,2,3))

现在我想查询所有拥有特定目标的 UserNotify 对象和至少特定通知级别(例如 2)的用户。

如果我这样做:

User.objects.filter(usernotify__target=desired_target,
    usernotify__notification_level__gte=2)

我得到所有具有指定目标的 UserNotify 对象和至少一个 Notification_level 大于或等于 2 的 UserNotify 对象的用户。但是,这两个 UserNotify 对象不必相同。

我知道我可以这样做:

user_ids = UserNotify.objects.filter(target=desired_target,
    notification_level__gte=2).values_list('user_id', flat=True)
users = User.objects.filter(id__in=user_ids).distinct()

但这一步对我来说似乎太过分了,我相信它会执行两个查询。

有没有办法通过单个查询来解决我的问题?

【问题讨论】:

我不确定我是否理解您的问题,您说您想要该查询并且该查询正在运行,具体问题是什么?你是什​​么意思相同的对象? 【参考方案1】:

实际上,鉴于usernotify 不是User 的有效字段名称,我不知道如何运行第一个查询。

您应该从 UserNotify 开始,就像您在第二个示例中所做的那样:

UserNotify.objects.filter(
    target=desired_target,
    notification_level__gte=2
).select_related('user').values('user').distinct()

【讨论】:

'field' usernotify 用于反向查找外键关系,就像在 Python 代码中可以使用 usernotify_set。 您提出的代码只给了我用户的 id,与我提出的解决方案相比没有任何优势,因为我仍然需要启动一个额外的查询来获取用户对象的信息。跨度> @TimSchneider 如果您需要其他(给定)字段并且您正在使用 postgres,请查看 trecouvr 答案。否则,您必须坚持使用第二个查询,除非您想纯粹在 python 中获得不同的用户。【参考方案2】:

我一直在寻找这种行为,但我从未找到比您描述的更好的方法(为用户 ID 创建查询并将其注入用户查询)。请注意,这还不错,因为如果您的数据库支持子查询,您的代码应该只触发一个由查询和子查询组成的请求。

但是,如果您只需要用户对象中的特定字段(例如 first_name),您可以尝试

qs = (UserNotify.objects
    .filter(target=desired_target, notification_level__gte=2)
    .values_list('user_id', 'user__first_name')
    .order_by('user_id')
    .distinct('user_id')
)

【讨论】:

这个语法只有 Postgres 支持 是的,我刚刚注意到它......当您使用 distinct(*fields) 语法时,您还必须提供一个 order_by,其中包含与不同字段匹配的第一个字段。 (docs.djangoproject.com/en/1.7/ref/models/querysets/…) 看来你是对的。我为这个问题提出的解决方案只创建一个带有一个子查询的查询。由于没有子查询似乎没有更好的解决方案,我想这是要走的路。感谢您的帮助。【参考方案3】:

我不确定我是否理解你的问题,但是:

class User(models.Model):
    … (Some fields irrelevant for this query)

class UserNotifiy(models.Model):
    user = models.ForeignKey(User, related_name="notifications")
    target = models.ForeignKey(<Some other Model>)
    notification_level = models.SmallPositivIntegerField(choices=(1,2,3))

然后

users = User.objects.select_related('notifications').filter(notifications__target=desired_target,
    notifications__notification_level__gte=2).distinct('id')

for user in users:
    notifications = [x for x in user.notifications.all()]

我现在手边没有我的 vagrant box,但我相信这应该可以。

【讨论】:

以上是关于Django过滤同一外键对象的两个字段的主要内容,如果未能解决你的问题,请参考以下文章

如果只有另一个模型的字段包含某个值并且这两个模型具有外键,我如何过滤模型的对象

Django的管理页面怎么显示和过滤另外一个表的字段,非外键

基于 Django 查询集中外键字段的 .count() 进行过滤

Django:每个外键返回一个过滤对象

Django admin 根据另一个字段值过滤一个外部字段

如何通过多对一关系中同一相关对象的两个属性在 django 中进行过滤?