Сделайте запрос с помощью 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])
Другие вопросы по тегам