Сделайте запрос с помощью activerecord, чтобы получать только пользователей из групп, а не из других
Я пытаюсь получить пользователей из нескольких групп (с заданными идентификаторами) и исключить пользователей из других групп.
Я пробовал что-то вроде:
User.joins(:groups).where(groups: {id: ["8939","8950"]}).where.not(groups: {id: 8942}).map(&:id)
User Load (0.9ms) SELECT "users".* FROM "users" INNER JOIN "groups_users" ON "groups_users"."user_id" = "users"."id" INNER JOIN "groups" ON "groups"."id" = "groups_users"."group_id" WHERE "groups"."id" IN (8939, 8950) AND "groups"."id" != $1 [["id", 8942]]
=> [119491, 119489, 119490, 119492, 119488, 119484, 119483, 119491, 119482]
Но это не правильно
Пользователи в группе 8942.
Group.find(8942).users.pluck(:id)
Group Load (0.4ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = $1 LIMIT 1 [["id", 8942]]
(0.6ms) SELECT "users"."id" FROM "users" INNER JOIN "groups_users" ON "users"."id" = "groups_users"."user_id" WHERE "groups_users"."group_id" = $1 [["group_id", 8942]]
=> [119490, 119492, 119491, 119457, 119423]
where.not
не работает на пользователя "groups"."id" != $1 [["id", 8942]]
, Зачем?
2 ответа
Правильный способ сделать это - использовать условие SQL EXISTS. Хотелось бы, чтобы для этого был специальный вспомогательный метод ActiveRecord, но пока его нет.
Ну, с использованием чистого SQL это нормально:
User.where("EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8939, 8950]).
where("NOT EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8942])
То, что вы делали со своим исходным запросом, - это не присоединяться к группам с [8942]
идентификаторы к вашему запросу, и только присоединение к группам с идентификаторами [8939, 8950]
, Ну, вы можете видеть прямо сейчас, что это не имеет никакого смысла: это все равно что просить выбрать каждого пользователя с именем bob
и не charlie
, Второе условие ничего не добавляет к первому.
Запрос на присоединение умножает столбцы, поэтому, если ваш пользователь входит в каждую группу, набор результатов будет следующим:
user_id | group_id
1 | 8939
1 | 8950
1 | 8942
Затем вы отфильтровываете последний ряд: 1 | 8942
, Тем не менее, пользователь 1
находится в наборе результатов и возвращается.
И попросить базу данных вернуть только те записи, которые не связаны с другим отношением, которое вы должны явно использовать NOT EXISTS
который существует явно для этой цели:)
Теперь есть драгоценный камень Where Exists, который вы можете использовать. (Полное раскрытие: я недавно создал этот драгоценный камень.)
С его помощью вы можете выполнить свою задачу так же просто, как:
User.where_exists(:groups, id: [1, 2]).where_not_exists(:groups, id: [3, 4])