在 Active Record (Rails) 中,如何从与每个父记录的 has_many 关系中 select 列的总和?
In Active Record (Rails), how to select a sum of a column from a has_many relation with every parent record?
考虑这个模型:
User:
id: int
Valuable:
id: int
user_id: int
value: int
用户有很多贵重物品。
事情是这样的。我想使用 ActiveRecord select 多个用户进行任何查询,之后我希望能够看到他们所有有价值的东西的总和,而不必进行 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 帮我计算。
更新: 我的意思是,我想依靠 PostgreSQL 的 SUM 方法来获得结果集中的总和。我认为我在使用 SELECT
和 GROUP BY
时所做的努力使这一点变得清晰。我不希望 Valuables table 中的任何数据或记录对象成为结果的一部分,因为它消耗太多内存并且使用 Ruby 计算字段只会使用太多 CPU 并且时间太长了。
方法一:
hash = User.joins(:valuables).group('users.id').sum('valuables.value')
hash.each { |uid, tval| puts "User ID #{uid} has total value #{tval}." }
请注意,哈希将只包含有贵重物品的用户的条目。要获得所有用户(即使是那些没有贵重物品的用户),您可以使用 includes
而不是 joins
。
方法二:
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
我会选择 方法 1 和 includes
。
编辑: 更好地理解问题后:
user_columns = User.column_names.join(', ')
users = User.
includes(:valuables).
group('users.id').
select("#{user_columns}, sum(valuables.value) AS total_value")
users.each { |u| puts "User ID #{user.id} has #{u.total_value} total value." }
原始 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)>
但是 "default_scope" 和 "I still want to be able to use includes" 的要求可能有点高,除非你想 manually load the associations。
考虑这个模型:
User:
id: int
Valuable:
id: int
user_id: int
value: int
用户有很多贵重物品。
事情是这样的。我想使用 ActiveRecord select 多个用户进行任何查询,之后我希望能够看到他们所有有价值的东西的总和,而不必进行 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 帮我计算。
更新: 我的意思是,我想依靠 PostgreSQL 的 SUM 方法来获得结果集中的总和。我认为我在使用 SELECT
和 GROUP BY
时所做的努力使这一点变得清晰。我不希望 Valuables table 中的任何数据或记录对象成为结果的一部分,因为它消耗太多内存并且使用 Ruby 计算字段只会使用太多 CPU 并且时间太长了。
方法一:
hash = User.joins(:valuables).group('users.id').sum('valuables.value')
hash.each { |uid, tval| puts "User ID #{uid} has total value #{tval}." }
请注意,哈希将只包含有贵重物品的用户的条目。要获得所有用户(即使是那些没有贵重物品的用户),您可以使用 includes
而不是 joins
。
方法二:
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
我会选择 方法 1 和 includes
。
编辑: 更好地理解问题后:
user_columns = User.column_names.join(', ')
users = User.
includes(:valuables).
group('users.id').
select("#{user_columns}, sum(valuables.value) AS total_value")
users.each { |u| puts "User ID #{user.id} has #{u.total_value} total value." }
原始 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)>
但是 "default_scope" 和 "I still want to be able to use includes" 的要求可能有点高,除非你想 manually load the associations。