如何根据多对多关系选择用户子集?
Posted
技术标签:
【中文标题】如何根据多对多关系选择用户子集?【英文标题】:How to select subset of users based on many-to-many relationship? 【发布时间】:2015-07-31 14:57:28 【问题描述】:User
和Organization
通过Relationship
具有多对多关联。
Relationship
模型中的一个变量是一个名为 member
的布尔值。user
可以在 Relationship
模型中多次出现,因为 user
可以与多个 organizations
相关联。
我如何从所有users
中选择一个随机实例 1) 与任何organization
没有关系(所以不会出现在Relationship
模型中)加上 2) 那些users
确实与组织有关系但member = false
为谁所有他们与组织的关系?
我在想这样的事情:
user = User.where( relationship_id = nil || ??? ).offset(rand(0..100)).first
【问题讨论】:
请输入关系代码,以便我们更好地了解您的具体情况。 谢谢,我已将代码添加到原帖中。 如果一个用户与一个组织有一个关系,并且成员 = true,那么是在这个选择中还是在这个选择之外? @Max,则它们不在选择范围内(要么根本没有关系,要么对于所有关系都有member = false
)。
这闻起来像那种例子,如果你用英语而不是代码解释了你真正想做的事情,那么很明显你甚至不需要做你正在做的事情询问。用常识的解释,你真正想得到什么?
【参考方案1】:
我会这样做
User.joins("LEFT JOIN relationships ON relationships.user_id = users.id").where('relationships.user_id IS NULL').offset(rand(0..100)).first
类似:
member_ids = Relationship.where(member: true).pluck(:user_id).uniq
users = User.where.not(id: member_ids) # or User.where('id NOT in (?)', member_ids) on Rails < 4
【讨论】:
第一个忽略不是任何上下文的成员,例如(所有关系上的成员 = false),也可以使用includes(:relationships)
而不是原始 SQL 连接来简化。第二个答案是我将如何处理。
您实际上不能使用includes
,因为包含关系的条件将不起作用:apidock.com/rails/v4.2.1/ActiveRecord/QueryMethods/includes(请参阅“条件”部分)。您可以添加一些样板文件(“引用”调用)以使其工作,但我认为首先使用“加入”更简洁。【参考方案2】:
因此,您真正想要的是不是任何组织成员的用户,其中“成为成员”意味着“具有成员 = true 的关系加入记录”。这是一个比您指定的条件简单得多的概念。
在这种情况下:
从 member = true 的关系中获取不同的 user_id 获取 ID 不在此列表中的用户例如
member_ids = Relationship.where(member: true).distinct.pluck(:user_id)
@users = User.where("id not in (?)", member_ids).all
【讨论】:
感谢 Max,这似乎应该符合我的要求。我没有测试它,因为播种失败。如果您也知道为什么会这样,我很乐意听到。 我会为此做一个不同的问题,因为这是一个单独的问题。但是,通常情况下,如果您向人们提供“失败”以外的零信息,他们将无法诊断您的问题;-) 好的,可以(一旦我可以发布另一个问题)。一旦我开始播种,我会报告代码是否有效。我将回滚这篇文章以消除播种问题。 请记住,此解决方案将要求数据库将每个关系记录的 ID 发送到您的 ruby 代码。然后你的 ruby 代码将不得不再次发送回去。自己制作数据库过滤器ID可能是一个更好的主意;) 在User.where("id not in (?)", member_ids)
中,rails 将清理 member_ids。另外,因为我是自己构建的,而不是仅仅从param
那里获得一些价值,所以 sql 注入甚至不是问题。【参考方案3】:
获取没有组织的用户:
User.where.not(id: Relationship.pluck(:user_id).uniq)
如果没有一些逻辑来比较用户的总关系与成员 = false 的关系,另一个查询看起来不像是你可以做的事情。
【讨论】:
【参考方案4】:现在您可以使用Where Exists gem。 (完全披露:我最近创建了那个宝石。)
# This will select all users, for which there are not relationships,
# or there is a relationship, but its 'member' attributes is false.
#
# Just what you've asked for.
users_to_select = User.where_not_exists(:relationships, member: true)
# 'offset' and 'count' will work as usual after 'where_not_exists'
random_user = users_to_select.offset(rand(users_to_select.count)).first
【讨论】:
【参考方案5】:嗯,使用 ActiveRecord 并不意味着您被禁止编写 SQL 代码。实际上,在许多情况下,您是要求这样做的。您的情况似乎是其中之一(您不能使用 ActiveRecord 进行“OR”查询,并且您没有 SQL 'WHERE EXISTS' 的语法糖)。
所以我建议你这样做:
free_users = User.where("NOT EXISTS (SELECT 1 FROM relationships WHERE user_id = users.id) OR NOT EXISTS (SELECT 1 FROM relationships WHERE user_id = users.id AND member = FALSE)")
random_user = User.find(free_users.ids.sample)
有关 SQL“EXISTS”的更多信息:
http://www.techonthenet.com/sql/exists.php
UPD。发布了另一个使用where_exists
gem 的答案。
【讨论】:
用户没有relationship_id @MiguelCunha 为什么我不应该呢? 但是我在提供的示例中没有 SQL 注入,您也不应该在 SQL 的 where 中使用它。如果您需要将一些参数推入原始 SQL,请使用占位符:thepugautomatic.com/2013/01/…(来自 Google 的第一个链接)。 Rails 会自动转义它们。在 Rails 中使用 SQL 片段是主流做法,您不必担心。实际上,你只能被 Ruby 接口提供的最简单的方法所束缚。 @MiguelCunha 这种方法可能注入到哪里?不需要用户输入。此外,当在其他一些答案中使用?
占位符时,rails 会适当地清理值,就好像它们作为哈希传递一样。
看看@Andrea Salicetti 的回答。还有this website is very interesting about injection in rails。您应该尽可能使用 Rails 函数。我知道存在一些限制,但您可以为此使用其他库,或者您可以(并且应该)重新考虑应用程序概念模型/架构。以上是关于如何根据多对多关系选择用户子集?的主要内容,如果未能解决你的问题,请参考以下文章