Rails - 不在特定关联旁边的联接 table 中的记录的范围

Rails - scope for records that are not in a join table alongside a specific association

我在 Rails 应用程序中有两个模型 - TournamentPlayer 通过联接关联 table:

class Tournament < ApplicationRecord

  has_many :tournament_players
  has_many :players, through: :tournament_players

end


class Player < ApplicationRecord

  has_many :tournament_players
  has_many :tournaments, through: :tournament_players

  scope :selected, -> (tournament) { includes(:tournaments).where(tournaments: {id: tournament.id}) }

end

我有很多锦标赛,每个锦标赛可以有很多玩家。玩家可以参加很多锦标赛。范围

scope :selected, -> (tournament) { includes(:tournaments).where(tournaments: {id: tournament.id}) }

成功找到所有已添加到锦标赛中的玩家,将锦标赛作为参数。

我想要的是一个相反的范围 - returns 所有尚未添加到给定锦标赛的玩家。我试过了

scope :not_selected, -> (tournament) { includes(:tournaments).where.not(tournaments: {id: tournament.id}) }

但是 returns 许多相同的玩家,我认为是因为这些玩家作为其他锦标赛的一部分存在。 SQL 看起来像这样:

SELECT "players".*, "tournaments”.* FROM "players" LEFT OUTER JOIN
"tournament_players" ON "tournament_players"."player_id" =
"players"."id" LEFT OUTER JOIN "tournaments" ON "tournaments"."id" =
"tournament_players"."tournament_id" WHERE ("tournaments"."id" != )
ORDER BY "players"."name" ASC  [["id", 22]]

我也试过 this question 上的建议 - 使用

scope :not_selected, -> (tournament) { includes(:tournaments).where(tournaments: {id: nil}) }

但这似乎不起作用 - 它只是 returns 一个空数组,我再次认为是因为玩家存在于加入 table 中作为单独锦标赛的一部分。 SQL 看起来像这样:

SELECT "players”.*, "tournaments”.* FROM "players" LEFT OUTER JOIN
"tournament_players" ON "tournament_players"."player_id" = 
"players"."id" LEFT OUTER JOIN "tournaments" ON "tournaments"."id" = 
"tournament_players"."tournament_id" WHERE "tournaments"."id" IS NULL 
ORDER BY "players"."name" ASC

好的,试试这个:

includes(:tournaments).distinct.where.not(tournaments: {id: tournament.id}) 

您需要做的是:

  1. 使用参考 table 进行左连接,并在比赛 ID 上附加一个条件,该 ID 与您要为
  2. 查找未选中玩家的 ID 相匹配
  3. 应用 WHERE 子句表明没有进行 JOIN。

这段代码应该可以做到:

# player.rb
scope :not_selected, -> (tournament) do 
  joins("LEFT JOIN tournament_players tp ON players.id = tp.player_id AND tp.tournament_id = #{tournament.id}").where(tp: {tournament_id: nil})
end

要是 Rails 有更好的方法来编写带有附加条件的 LEFT JOIN 查询就好了...

一些注意事项:

  1. 不要加入实际关系(即锦标赛),它会显着降低查询性能,而且没有必要,因为所有条件先决条件都在参考 table 中。此外,您感兴趣的所有行 return 来自锦标赛 table.
  2. 的 NULL 数据
  3. 不要使用 eager_load。除了据我所知它不支持自定义条件,它会为所有你不需要的相关对象创建模型。