Rails,遍历组查询的结果

时间:2017-01-12 16:36:16

标签: sql ruby-on-rails activerecord

我很抱歉,如果这已经存在,我看了但是找不到这个确切的问题。

假设我有三个ActiveRecord模型,ContinentCountryTownTown有一个属性population,我有兴趣通过对各个城镇的人口进行求和来分解每个国家的人口,按大陆分组。

因为一个国家有很多城镇,但是大陆上的国家相对较少,而且只有几个大陆,我可能会继续这样做:

continents = {}
Continent.each do |continent|
  country_breakdown = {}
  continent.countries.each do |country|
    country_breakdown[country] = country.towns.sum(:population)
  end
  continents[continent] = country_breakdown
end

...然后在视图中迭代continents

问题是这有SQL复杂性O(n),其中n是国家/地区。

我的问题是:有没有办法让SQL生成(在单个查询中)这个分组结果,然后我们可以像在Rails视图端的普通嵌套Hash一样迭代?

我尝试过使用ActiveRecord::QueryMethods#group,但我对如何迭代结果感到困惑。

1 个答案:

答案 0 :(得分:2)

是的,有。假设你完全描述了你的关系:

class Continent
  has_many :countries
  has_many :towns, through: :countries
end

class Country
  belongs_to :continent

  has_many :towns
end

class Town
  belongs_to :country

  has_one :continent, through: :country
end

您应该可以运行:

results = Continent.joins(:town).group(:continent_id, :country_id).sum(:population)

这会以[[continent_id, country_id], population]格式为您提供哈希值。

e.g. {[2, 38]=>39993, [1, 31]=>127425, [5, 12]=>113556, [5, 76]=>2966, [1, 10]=>263473, [1, 34]=>154492, [2, 37]=>55087...}

通过加入,您可以访问查询中的Town模型,Continenthas_many through:关系而知道该模型。

您可以迭代该哈希以获得您想要的格式的数据:

formatted = results.each_with_object({}) do |((continent_id, country_id), pop), m|
  m[continent_id] ||= {}
  m[continent_id][country_id] = pop
end

e.g. { continent_id => { country_id => population } }
   {2=>{38=>39993, 37=>55087}, 1=>{31=>127425, 10=>263473...}

执行此步骤的方法可能更为有效。将每个条目映射到一个真实对象可能很有用,这样您就不会错过哪个数字指的是哪个值。但无论如何,这会将您的数据库访问减少到一个查询,这将比原始实现快得多。