在Active Record(Rails)中,如何从has_many关系中为每个父记录选择一列的总和?

时间:2016-02-05 19:58:44

标签: ruby-on-rails postgresql rails-activerecord aggregate-functions

考虑这个模型:

User:
  id: int

Valuable:
  id: int
  user_id: int
  value: int

用户有很多贵重物品。

现在就是这样。我想使用ActiveRecord选择任何查询的多个用户,之后我希望能够看到所有贵重物品的总和,,而不必进行N + 1次查询。

所以,我希望能够做到这一点:

# @var ids [Array] A bunch of User IDs.
User.where(id: ids).each do |u| 
  puts "User ##{u.id} has total value of #{u.total_value}
end

它应该执行1(或最多2)个查询,而不是实例化所有贵重物品。我试过玩select('*, SUM(valuables.value) as total_value).joins(valuables),但没有运气。我正在使用PostgreSQL

如果可能的话,我希望这会自动发生(例如使用default_scope),我仍然希望能够使用includes

更新:抱歉,我还不清楚这一点。 (实际上,我确实写过)。我希望实例化所有贵重物品。我想让PostgreSQL为我做计算。

UPDATE:我的意思是,我想依赖PostgreSQL的SUM方法来获得结果集中的总和。我认为我使用SELECTGROUP BY的努力清楚了。我不希望Valuables表中的任何数据或记录对象成为结果的一部分,因为它占用了太多内存并且使用Ruby计算字段只会占用太多CPU而且耗时太长。

2 个答案:

答案 0 :(得分:3)

在原始SQL中你需要这样的东西:

SELECT users.*, SUM(valuable.value) AS values_sum
FROM users
LEFT OUTER JOIN valuables ON users.id = valuables.user_id
GROUP BY users.id

因此,在ActiveRecord查询中翻译它将如下所示:

User.select('users.*, SUM(valuables.value) AS total_value')
    .joins('LEFT OUTER JOIN valuables ON users.id = valuables.user_id')

请注意,您实际上并未从valuables中选择任何列。

[10] pry(main)> @users = User.select('users.*, SUM(valuables.value) AS total_value').joins('LEFT OUTER JOIN valuables ON users.id = valuables.user_id')

  User Load (1.4ms)  SELECT users.*, SUM(valuables.value) as total_value FROM "users" LEFT OUTER JOIN valuables ON users.id = valuables.user_id
=> [#<User:0x007f96f7dff6e8
  id: 8,
  created_at: Fri, 05 Feb 2016 20:36:34 UTC +00:00,
  updated_at: Fri, 05 Feb 2016 20:36:34 UTC +00:00>]
[11] pry(main)> @users.map(&:total_value)
=> [6]
[12] pry(main)> 

然而,除非你想要manually load the associations,否则“default_scope”和“我仍然希望能够使用包含”要求可能有点高。

答案 1 :(得分:2)

方法1

"Locations":{
  [groupName]:{
    [user]:{
      lat: "175.48598907719583",
      lng: "-37.99470968516994",
      time: "1454707169089"
     },
  },
}

请注意,哈希只包含有贵重物品的用户的条目。要获取所有用户(即使没有贵重物品的用户),您可以使用hash = User.joins(:valuables).group('users.id').sum('valuables.value') hash.each { |uid, tval| puts "User ID #{uid} has total value #{tval}." } 代替includes

方法2

joins

我会选择方法1 value_hash = Valuable.group(:user_id).sum(:value) all_user_ids = User.pluck(:id) all_user_ids.each do |uid| tval = value_hash[uid] || 0 puts "User ID #{uid} has total value #{tval}." end

编辑:在更好地理解问题之后:

includes