Rails - получить объекты, родительский идентификатор которых отсутствует ни в одной из дочерних ассоциаций. - PullRequest
0 голосов
/ 06 октября 2018

У меня есть две эти модели:

Collabs
- id
- title
# has_many :collaborations

Collaborations
- collab_id
- user_id
- status
# belongs_to :collab
# belong_to :user

В запросе я не хочу получать все коллаборации, где определенный user_id НЕ присутствует в дочерней (коллаборации) ассоциации.У колла может быть ноль ко многим коллаборациям, и коллаборации будут иметь разные user_id.

Я пробовал (используя области):

collabs = Collab.available_for_user(2)

scope :available_for_user, -> (user_id) { joins(:collaborations).where.not(collaborations: {user_id: user_id}) }

Я также пробовал:

scope :available_for_user, -> (user_id) { left_outer_joins(:collaborations).where.not(collaborations: {user_id: user_id}) }

Это SQL, который выводится в консоли:

SELECT "collabs".* FROM "collabs" LEFT OUTER JOIN "collaborations" ON "collaborations"."collab_id" = "collabs"."id" WHERE ("collaborations"."user_id" != $1)  [["user_id", 13]]

Это работает, если дочерние ассоциации имеют только предоставленный user_id, но коллаб имеет другое сотрудничество с другим user_id, тогда этоколлаб доставляется с помощью областей, описанных выше.

Ответы [ 3 ]

0 голосов
/ 07 октября 2018

Вы можете легко использовать where.not вместо where

scope :available_for_user, -> (user_id) { joins(:collaborations).where.not(collaborations: {user_id: user_id}) }
0 голосов
/ 07 октября 2018

Другой вариант - сгруппировать Collaborations s по collab_id, затем использовать функцию агрегирования, чтобы отобразить все идентификаторы пользователей для каждого Collab и проверить это:

scope :available_for_user, -> (user_id) {
  joins(<<~CUSTOM_SQL_JOIN
    INNER JOIN (
      SELECT collab_id, array_agg(user_id) as user_ids FROM collaborations GROUP BY collab_id
    ) collaborations_by_collab ON #{user_id} IN collaborations_by_collab.user_ids
  CUSTOM_SQL_JOIN
  )
}

Ref: https://www.postgresql.org/docs/9.5/static/functions-aggregate.html

0 голосов
/ 06 октября 2018

Это ваш звонок:

Collab.find_by_sql [
  "SELECT *
  FROM collabs
  WHERE id NOT IN (
      SELECT C.id
      FROM collabs C
      JOIN collaborations CL ON CL.collab_id = C.id
      WHERE CL.user_id = :user_id)",
  { user_id: user_id }
]

Объяснение: Чтобы выбрать колбы, у которых нет коллабораций для выбранного user_id, необходимо найти те, у которых есть коллаборации, и исключить их.:)

Без SQL, с простым Ruby, он может быть записан как:

Collab.where.not(
  id: Collab.joins(:collaborations)
            .where(collaborations: { user_id: user_id })
            .pluck(:id)
)

Или, с более подробной информацией:

# Inner query to find collabs with collaborations for given user_id:
#   SELECT C.id
#   FROM collabs C
#     JOIN collaborations CL ON CL.collab_id = C.id
#   WHERE CL.user_id = :user_id
ids = Collab.joins(:collaborations)
            .where(collaborations: { user_id: user_id })
            .pluck(:id)

# Final query:
#   SELECT *
#   FROM collabs
#   WHERE id NOT IN :ids
Collab.where.not(id: ids)

Он производит два вызова SQLвместо одного в первом случае (find_by_sql), поэтому для повышения производительности, пожалуйста, избегайте этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...