Как выбрать подмножество пользователей на основе отношения "многие ко многим"?
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
5 ответов
Я сделаю это
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
Итак, что вы на самом деле хотите, так это пользователей, которые не являются членами каких-либо организаций, где "членство" означает "наличие записи присоединения к отношениям, где member = true". Это гораздо более простая концепция, чем указанные вами условия.
В таком случае:
- получить отличный user_id от отношений, где member = true
- получить пользователей, чей идентификатор НЕ в этом списке
например
member_ids = Relationship.where(member: true).distinct.pluck(:user_id)
@users = User.where("id not in (?)", member_ids).all
Для получения пользователей без организации:
User.where.not(id: Relationship.pluck(:user_id).uniq)
Другой запрос не выглядит как что-то, что вы могли бы делать без логики, чтобы сравнить общее количество отношений для пользователя с теми, где member = false.
Теперь есть драгоценный камень Where Exists, который вы можете использовать. (Полное раскрытие: я недавно создал этот драгоценный камень.)
# 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
Что ж, использование ActiveRecord не предполагает, что вам запрещено писать код SQL. На самом деле, вы обязаны сделать это во многих случаях. Ваш случай кажется одним из них (вы не можете выполнить запрос "ИЛИ" с ActiveRecord, и у вас нет синтаксического сахара для SQL "ГДЕ СУЩЕСТВУЕТ").
Поэтому я предлагаю вам сделать что-то вроде:
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
драгоценный камень.