1

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

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)实际上是按照我的方式做的,我注意到日志都在说“缓存加载”-rails 在这里为我做了一些魔术吗?我什么都不担心吗?

4

1 回答 1

2

Rail 的Eager Loading在这里应该有所帮助。

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

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

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

仅将每个报告的第一个图表加载到内存中需要额外的工作:

  • 添加一个额外的列reports.first_chart_id
  • 定义一个额外的关联 from ReporttoChartfirst_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

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

高温高压

于 2013-02-12T11:10:36.327 回答