我有一个Rails应用程序,用户可以选择他们正在观看的节目,以及他们已经观看过的剧集。 ActiveRecord关联看起来像这样:
class User
has_many :episodes_joins, :dependent => :delete_all, class_name: 'UsersEpisodesJoin'
has_many :episodes, through: :episodes_joins
has_many :shows_joins, :dependent => :delete_all, class_name: 'UsersShowsJoin'
has_many :shows, through: :shows_joins
end
class Show
has_many :episodes, dependent: :destroy
has_many :users_shows_joins, :dependent => :delete_all
has_many :users, through: :users_shows_joins
end
class Episode
belongs_to :show
has_many :users_episodes_joins, :dependent => :delete_all
has_many :users, through: :users_episodes_joins
end
class UsersShowsJoin
belongs_to :user
belongs_to :show
end
class UsersEpisodesJoin
belongs_to :user
belongs_to :episode
end
我希望允许用户过滤"特殊剧集"每个节目单独("特别剧集"有episode.special?设置为true)。为此,我考虑在名为filter_specials
的UsersShowsJoins表中添加一个布尔列。我现在想做这样的事情:
episodes = user.episodes.where(special: false).to_a
user.episodes.where(special: true) do |epi|
show_join = UsersShowsJoins.where(user_id: user.id, show_id: epi.show.id).first
episodes << epi if show_join.nil? || !show_join.filter_specials?
end
# do something with 'episodes',
# which contains all non-special episodes,
# as well as special episodes of shows for which the user is not filtering these
我知道这是一个非常虚伪和缓慢的实现,会执行大量的数据库查询,而且我确信有更好的方法可以做到这一点,甚至可能只需要一个查询。
此外,我希望能够在数据库中查询用户选择的所有节目,并为同一用户预加载具有UsersEpisodesJoins行的相应剧集。类似的东西:
shows = user.shows.all
h = {}
shows.each do |show|
episodes = user.episodes.where(show_id: show.id).all
h[show] = episodes
end
# do something with h,
# which contains a user's watched episodes for each selected show
如何有效地编写这些查询,以免我在复杂查询中遇到N + 1问题?
答案 0 :(得分:2)
您应该能够像这样编写特殊剧集过滤器:
show_joins = UsersShowsJoins.joins(:shows, :users).where(episodes: { special: true})
这将按照他们的共同关系加入shows
和users
,并过滤special
设置为true
的剧集。
根据您想要使用的对象类型作为主要对象,您可以编写各种变体:
shows = Show.joins(:users, :episodes).where(episodes: {special: true})
或:
episodes = Episode.joins(shows: :users).where(special: true)
对于第二个查询,您可以使用:
user.shows.includes(:episodes).all
这应预先加载用户正在观看的节目的剧集。如果您愿意,可以添加where
条件,group
- 甚至order
条件,如下所示:
user.shows.includes(:episodes).order("shows.title")
按节目标题排序结果(假设有一个节目标题字段)。
Active Record Query Interface指南在Joining Tables部分中有一些很好的示例。值得一读的是如何有效地进行这样复杂的查询。