Как выбрать подмножество пользователей на основе отношения "многие ко многим"?

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 ответов

Решение

Я сделаю это

  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 
    

Итак, что вы на самом деле хотите, так это пользователей, которые не являются членами каких-либо организаций, где "членство" означает "наличие записи присоединения к отношениям, где 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 драгоценный камень.

Другие вопросы по тегам