如何在Active Record中实现此加入?

时间:2013-02-12 10:26:52

标签: ruby-on-rails rails-activerecord

我正在开发一个具有以下关联的应用程序:

class Chart < ActiveRecord::Base
  belongs_to :chart_group
  has_many :charts, :through => :chart_groups
end

class ChartGroup < ActiveRecord::Base
  has_many :charts
  belongs_to :report
end

class Chart < ActiveRecord::Base
  belongs_to :chart_group
end

应用程序的一部分要求我们显示前20个报告,并且对于每个报告,我们显示第一个图表(作为快照的种类)。他们这样做的方式是,在控制器中选择报告;

@reports = Report.limit(20)

然后在视图中做;

@reports.each do |report|
   <%= report.charts.first.title %>
end

这似乎很好并且运行速度很快,但是与“N + 1查询问题”相违背 - 我会在每个报告中获得一次数据库往返,所以21次点击。所以我有几个问题

1)如何更改原始查询以获取第一个图表?我尝试使用“.joins”,但问题是可以存在与任何一个报告关联的图表负载,并且可能有数百个报告。我无法弄清楚如何在声明中包含“仅第一张图表”部分。

2)实际上按照我的方式做,我注意到日志都在说“CACHE LOAD” - 轨道在这里为我做了一些魔术吗?我什么都不担心?

1 个答案:

答案 0 :(得分:2)

铁路的Eager Loading应该有帮助。

以下内容应加载与请求的报告相关联的所有图表:

@reports = Report.limit(20).includes(:charts)

AR将加载所有请求的报告limit(20)并查找与这些报告相关联的所有图表,并将这些图表加载到内存中。

要为每个报告仅将第一个图表加载到内存中,需要执行其他操作:

  • reports.first_chart_id
  • 添加其他列
  • 定义从ReportChartfirst_chart_id相关的其他关联

报告应修改如下:

class Report < ActiveRecord::Base
  belongs_to :first_chart, :foreign_key => 'first_chart_id', :class_name => 'Chart'
end

然后:

@reports = Report.limit(20).includes(:first_chart)

上述唯一的警告是,必须在Report实例创建/更新期间设置first_chart_id。

另一种解决方案是执行两个数据库查询:

获取所有报告然后迭代获取的报告,提取@ report.id然后查询图表以仅返回第一个图表(不知道第一个图表是如何定义的)

@reports = Report.limit(20)
@report_ids = @reports.collect{|r| r.id}
@first_charts = Chart.find_by_report_ids(@report_ids)


class Chart
  def self.find_by_report_ids(report_ids)
    {}.tap do |result|
      #not sure what your scheme is like for Report and Chart... so guessing  
      where(:id => report_ids).group(:report_id).each do |chart|
        result[chart.report_id] = chart
      end
    end
  end
end

find_by_report_ids返回由report.id索引的图表哈希值,可让您快速访问视图中报表的第一个图表

@reports.each do |report|
   <%= @first_charts[report.id].title %>
end

不幸的是,有时你必须为多对多类型关系编写额外的代码,但这些关系可以通过使用两次数据库跳转来解决,这仍然比O(N)查询要好得多。

HTH