如何在Django中执行双连接关系的查询?

5 投票
1 回答
1232 浏览
提问于 2025-04-16 10:41

应该有办法通过ORM来完成这个查询,但我现在没有找到。

设置情况

我在建模的内容是:一个租户可以住多个房间,而一个用户可以拥有多个房间。所以房间有一个指向租户的外键(FK)和一个指向用户的外键。房间的维护者也可能是一个不同的用户。

也就是说,我有这些(简化的)模型:

class Tenant(models.Model):
    name = models.CharField(max_length=100)

class Room(models.Model):
    owner = models.ForeignKey(User)
    maintainer = models.ForeignKey(User)
    tenant = models.ForeignKey(Tenant)

问题

给定一个租户,我想要找出拥有他们所住房间的用户。

相关的SQL查询应该是:

SELECT auth_user.id, ...
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id;

从相关的用户对象中获取任何单个值可以通过,比如说,my_tenant.rooms.values_list('owner__email', flat=True)来完成,但获取用户的完整查询集让我感到困惑。

通常,解决这个问题的一种方法是在我的Tenant模型上设置一个指向UserManyToMany字段,并使用TenantRoom作为“中介”模型。不过在这种情况下,这样做不太合适,因为TenantRoom模型还有一个指向User的第二个(无关的)外键(见“限制”)。而且这似乎会让租户模型变得杂乱。

使用my_tenant.rooms.values_list('user', flat=True)接近目标,但返回的是用户ID的值列表,而不是实际的用户对象查询集。

问题

所以:有没有办法通过ORM,只用一个查询就获取实际模型实例的查询集?


编辑

如果确实没有办法直接通过ORM在一个查询中做到这一点,那么实现我想要的结果的最佳方式是什么(在性能、习惯用法、可读性等方面的组合)?我看到的选项有:

  1. 子查询

    users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
    
  2. 通过Python进行子查询(见性能考虑,了解背后的原因)

    user_ids = id__in=my_tenant.rooms.values_list('user')
    users = User.objects.filter(id__in=list(user_ids))
    
  3. 原始SQL:

    User.objects.all("""SELECT auth_user.*
    FROM tenants_tenant, tenants_room, auth_user
    WHERE tenants_tenant.id = tenants_room.tenant_id
    AND tenants_room.owner_id = auth_user.id""")
    
  4. 其他...?

1 个回答

3

正确的做法是使用 related_name

class Tenant(models.Model):
    name = models.CharField(max_length=100)

class Room(models.Model):
    owner = models.ForeignKey(User, related_name='owns')
    maintainer = models.ForeignKey(User, related_name='maintains')
    tenant = models.ForeignKey(Tenant)

这样你就可以这样做:

jrb = User.objects.create(username='jrb')
bill = User.objects.create(username='bill')
bob = models.Tenant.objects.create(name="Bob")
models.Room.objects.create(owner=jrb, maintainer=bill, tenant=bob)

User.objects.filter(owns__tenant=bob)

撰写回答