Ruby on rails Rails-获取父id不在任何子关联中的对象

Ruby on rails Rails-获取父id不在任何子关联中的对象,ruby-on-rails,join,Ruby On Rails,Join,我有这两种型号: Collabs - id - title # has_many :collaborations Collaborations - collab_id - user_id - status # belongs_to :collab # belong_to :user 在查询中,我不想获取所有协作,因为子协作关联中不存在某个用户id。一个协作可以有零到多个协作,并且协作将具有不同的用户id 我尝试过使用作用域: collabs = Collab.available_for_us

我有这两种型号:

Collabs
- id
- title
# has_many :collaborations

Collaborations
- collab_id
- user_id
- status
# belongs_to :collab
# belong_to :user
在查询中,我不想获取所有协作,因为子协作关联中不存在某个用户id。一个协作可以有零到多个协作,并且协作将具有不同的用户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]]
如果子关联仅具有提供的用户id,但如果协作具有另一个与另一个用户id的协作,则可以使用上述作用域获取此协作。

这是您的调用:

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 }
]
说明:要获取所选用户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调用,而不是第一种情况下的一个SQL调用,因此为了性能起见,请避免这样做。

您可以轻松地使用where.not代替where


另一个选项是按collab_id对协作进行分组,然后使用聚合函数映射每个协作的所有用户id并进行检查:

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:

这是MySQL、postgresql、…@codenamev吗?我正在使用postgresOkay,但为什么最后一个调用了两次,而第一个调用了一次?第一次调用-查找具有给定用户id的协作的协作id。第二次调用-排除它们。效果很好,谢谢。我还想知道如何在普通ruby中进行相同的调用,而不进行两次调用,如果有办法的话是的,这很有意义!对不起,这是我的问题,只是打错了。编辑它
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
  )
}