如何优化此ActiveRecord查询?

时间:2013-09-04 07:57:51

标签: sql ruby-on-rails postgresql activerecord

我每天都在学习ruby,rails和ActiveRecord。现在,我正在通过我正在构建的一个新的小应用程序来学习SQL,但问题是我的应用程序的主视图目前每页刷新大约2000个查询,oouuuppps。

所以现在我知道我的数据库中有所有必需的信息并且我可以正确显示它们,现在是我优化它们的时候了但是我不知道从哪里开始说实话。

这些是我的模特协会

class League < ActiveRecord::Base
  belongs_to :user
  has_many :league_teams
  has_many :teams, :through => :league_teams
end


class Team < ActiveRecord::Base
  has_many :gameweeks
  has_many :league_teams
  has_many :leagues, :through => :league_teams
end


class Gameweek < ActiveRecord::Base
  belongs_to :team
  has_and_belongs_to_many :players
  has_and_belongs_to_many :substitutes, class_name: "Player", join_table: "gameweeks_substitutes"
  belongs_to :captain, class_name: "Player"
  belongs_to :vice_captain, class_name: "Player"
end


class Player < ActiveRecord::Base
  serialize :event_explain
  serialize :fixtures
  serialize :fixture_history
  has_many :gameweeks, class_name: "captain"
  has_many :gameweeks, class_name: "vice_captain"
  has_and_belongs_to_many :gameweeks
  has_many :player_fixtures
end

所以这是我的控制器:

@league = League.includes(teams: [{gameweeks: [{players: :player_fixtures} , :captain]}]).find_by(fpl_id:params[:fpl_id])
@teams = @league.teams
@defense_widget_leaderboard = @league.position_based_leaderboard_stats(@teams, ['Defender', 'Goalkeeper'])

这是我联盟模式中的一种方法:

  def position_based_leaderboard_stats(teams,positions_array)
    leaderboard = []

    teams.each do |team|
      position_points = 0
      gameweeks = team.gameweeks

      gameweeks.each do |gameweek|
        defense = gameweek.players.where(type_name:positions_array)

        defense.each do |player|
          player.player_fixtures.where(gw_number: gameweek.number).each do |p|
            position_points += p.points
          end
        end
      end
      leaderboard << [team.team_name,position_points]
    end
    return leaderboard.sort_by {|team| team[1]}.reverse
  end

我有4种方法看起来或多或少与上面的方法相同。每个人都在进行300到600次查询。

据我所知,这是N + 1查询的典型案例。我试图减少@league中的包含但是它让我从2000到1800查询。

我调查了group_byjoinssum,但我无法使其发挥作用。

我最接近工作的是这个

players = PlayerFixture.group("player_id").sum(:points)

然后我可以通过players[player.id]进行查询,但这并不能给我正确的结果,因为它没有考虑到Gameweeks&gt;玩家&gt; Player_fixtures关系。

如何减少我正在进行的查询次数?我在freenode上继续#RubyOnRails,人们告诉我它可以在一个查询中完成,但不会指向我任何方向或帮助我...

由于

2 个答案:

答案 0 :(得分:0)

出现position_based_leaderboard_stats N + 1问题。因此,您可以在each周期之前预加载所有关联:

def position_based_leaderboard_stats(teams,positions_array)
  leaderboard = []
  Team.preload(gameweeks: players).where('players.type_name=?', positions_array )
  your code 

另外,你可以将player_fixtures添加到preload语句,但我无法理解这些关联的依赖关系,抱歉。

答案 1 :(得分:0)

花一些时间在SQL上。终于找到了可以帮助我的查询。我还发现了SQL视图以及如何use them through Activerecord非常整洁。

最终成功查询

CREATE VIEW team_position_points AS

select teams.id as team_id, teams.team_name, players.type_name, sum(points) as points
from teams 
inner join gameweeks on teams.id = gameweeks.team_id    
inner join gameweeks_players on gameweeks.id = gameweeks_players.gameweek_id
inner join players on gameweeks_players.player_id = players.id
inner join player_fixtures on players.id = player_fixtures.player_id AND player_fixtures.gw_number = gameweeks.number
group by teams.id, players.type_name