如何在 Django 中按照双连接关系执行查询(或者:如何绕过 Django 对 ManyToMany“通过”模型的限制?)

发布于 2024-10-14 15:24:30 字数 2083 浏览 5 评论 0 原文

一定有一种方法可以通过 ORM 执行此查询,但我没有看到它。

设置

这就是我正在建模的内容:一名租户可以占用多个房间,一名用户可以拥有多个房间。因此,房间有一个针对租户的 FK 和针对用户的 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;

从相关的 User 对象中获取任何单独的值可以使用 my_tenant.rooms.values_list('owner__email', flat=True) 来完成,但是获取完整的值用户的查询集让我很困惑。

通常解决此问题的一种方法是在我的 Tenant 模型上设置一个 ManyToMany 字段,指向 UserTenantRoom > 作为“通过”模型。但在这种情况下,这不起作用,因为 TenantRoom 模型有第二个(不相关的)ForeignKeyUser(请参阅“限制” )。另外,租户模型上似乎出现了不必要的混乱。

执行 my_tenant.rooms.values_list('user', flat=True) 让我很接近,但返回的是用户 ID 的 ValuesListQuerySet,而不是实际 User 对象的查询集。

问题

那么:有没有一种方法可以通过 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.objects.filter(id__in=list(user_ids))
    
  3. 原始 SQL:

    User.objects.all("""SELECT auth_user.*
    来自租户_租户、租户_房间、auth_用户
    其中租户_租户.id = 租户_房间.租户_id
    AND租户_room.owner_id = auth_user.id""")
    
  4. 其他...?

There must be a way to do this query through the ORM, but I'm not seeing it.

The Setup

Here's what I'm modelling: one Tenant can occupy multiple rooms and one User can own multiple rooms. So Rooms have an FK to Tenant and an FK to User. Rooms are also maintained by a (possibly distinct) User.

That is, I have these (simplified) models:

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)

The Problem

Given a Tenant, I want the Users owning a room which they occupy.

The relevant SQL query would be:

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;

Getting any individual value off the related User objects can be done with, for example, my_tenant.rooms.values_list('owner__email', flat=True), but getting a full queryset of Users is tripping me up.

Normally one way to solve it would be to set up a ManyToMany field on my Tenant model pointing at User with TenantRoom as the 'through' model. That won't work in this case, though, because the TenantRoom model has a second (unrelated) ForeignKey to User(see "restictions"). Plus it seems like needless clutter on the Tenant model.

Doing my_tenant.rooms.values_list('user', flat=True) gets me close, but returns a ValuesListQuerySet of user IDs rather than a queryset of the actual User objects.

The Question

So: is there a way to get a queryset of the actual model instances, through the ORM, using just one query?


Edit

If there is, in fact, no way to do this directly in one query through the ORM, what is the best (some combination of most performant, most idiomatic, most readable, etc.) way to accomplish what I'm looking for? Here are the options I see:

  1. Subselect

    users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
    
  2. Subselect through Python (see Performance considerations for reasoning behind this)

    user_ids = id__in=my_tenant.rooms.values_list('user')
    users = User.objects.filter(id__in=list(user_ids))
    
  3. Raw 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. Others...?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

弱骨蛰伏 2024-10-21 15:24:30

正确的方法是使用 lated_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)

The proper way to do this is with 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)

Then you can do this:

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文