我正在开发一个具有以下关联的应用程序:
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” - 轨道在这里为我做了一些魔术吗?我什么都不担心?
答案 0 :(得分:2)
铁路的Eager Loading应该有帮助。
以下内容应加载与请求的报告相关联的所有图表:
@reports = Report.limit(20).includes(:charts)
AR将加载所有请求的报告limit(20)
并查找与这些报告相关联的所有图表,并将这些图表加载到内存中。
要为每个报告仅将第一个图表加载到内存中,需要执行其他操作:
reports.first_chart_id
Report
到Chart
与first_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