如何基于多对多关系select用户子集?

How to select subset of users based on many-to-many relationship?

UserOrganization 通过 Relationship.

存在多对多关联

Relationship 模型中的一个变量是一个名为 member 的布尔值。
user 可以在 Relationship 模型中多次出现,因为 user 可以与多个 organizations.

相关

我如何 select 来自所有 users 的随机实例 1) 与任何 organization 没有关系(所以不要出现在 Relationship 模型) 加上 2) 那些 users 确实与组织有关系但 member = false 对于 所有 他们与组织的关系?

我在想类似的东西:

user = User.where( relationship_id = nil || ??? ).offset(rand(0..100)).first

嗯,使用 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.

的答案

获取没有组织的用户:

User.where.not(id: Relationship.pluck(:user_id).uniq)

如果没有一些逻辑来比较用户的总关系与 member = false 的关系,另一个查询似乎不是您可以做的事情。

因此,您真正想要的是不属于任何组织的用户,其中 "being a member" 表示 "having a relationship join record where member = 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

我会这样做

  1. User.joins("LEFT JOIN relationships ON relationships.user_id = users.id").where('relationships.user_id IS NULL').offset(rand(0..100)).first

  2. 类似于:

    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 
    

现在有一个 Where Exists gem 可供您使用。 (完全披露:我最近创建了 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