【问题标题】:Rails - scope for records that are not in a join table alongside a specific associationRails - 不在特定关联旁边的连接表中的记录的范围
【发布时间】:2017-01-18 10:55:42
【问题描述】:

我在 Rails 应用中有两个模型 - TournamentPlayer 通过连接表关联:

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}) }

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

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

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

但这会返回许多相同的玩家,我认为是因为这些玩家作为其他锦标赛的一部分存在。对应的 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" != $1)
ORDER BY "players"."name" ASC  [["id", 22]]

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

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

但这似乎不起作用 - 它只是返回一个空数组,我再次认为是因为玩家作为单独锦标赛的一部分存在于连接表中。对应的 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

【问题讨论】:

标签: sql ruby-on-rails ruby rails-activerecord jointable


【解决方案1】:

你需要做的是:

  1. 与参考表进行左连接,并在锦标赛 ID 上添加一个附加条件,以匹配您要为其查找未选中玩家的条件
  2. 应用 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. 不要加入实际关系(即锦标赛),它会显着降低查询的性能,而且没有必要,因为所有条件先决条件都在引用表中。此外,您感兴趣的所有行都会从锦标赛表中返回 NULL 数据。
  2. 不要使用eager_load。除了据我所知,它不支持自定义条件外,它还会为所有相关对象创建模型,而您不需要。

【讨论】:

  • 谢谢!它虽然抛出了一些错误。首先是Player 不知道传递给find_not_in_tournament 的参数tournament_id 是什么。第二个是find_not_in_tournament是未定义的?范围也缺少开头do
  • 废话 - 我不清楚一开始的 find_not_in_tournament 方法调用的用途是什么,但没有它,它可以完美地工作。如果您对其进行编辑以包含缺少的do 并删除find_not_in_tournament,我会将其标记为正确答案。谢谢!
  • 我从头到尾写了这个,我先写了一个方法,然后我读到你需要的是一个范围。我想我很着急。感谢您的更新。
【解决方案2】:

好的,试试这个:

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

【讨论】:

  • 谢谢 - 但:not_selected 范围会抛出此错误:ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "tournaments"
  • 你能改吗,而不是includesjoins换成@?
  • 不 - 得到这个错误:ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "tournaments.id" must appear in the GROUP BY clause or be used in an aggregate function
  • 你可以试试第二个选项吗?
  • 那个只是返回一个空数组。
猜你喜欢
  • 2022-10-01
  • 2011-04-21
  • 1970-01-01
  • 1970-01-01
  • 2022-12-18
  • 1970-01-01
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
相关资源
最近更新 更多