如何根据多对多关系选择用户子集?

Posted

技术标签:

【中文标题】如何根据多对多关系选择用户子集?【英文标题】:How to select subset of users based on many-to-many relationship? 【发布时间】:2015-07-31 14:57:28 【问题描述】:

UserOrganization 通过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 函数。我知道存在一些限制,但您可以为此使用其他库,或者您可以(并且应该)重新考虑应用程序概念模型/架构。

以上是关于如何根据多对多关系选择用户子集?的主要内容,如果未能解决你的问题,请参考以下文章

根据多对多关系确定选择的选项

Django 包含多对多字段

通过 SQLAlchemy 选择多个多对多关系

选择多对多关系的最新记录

雄辩的多对多对多 - 如何轻松加载远距离关系

如何在 WinForms 中绑定多对多关系?