Rails 6使用reduce生成嵌套哈希计算各区域chart_axis_values平均值
核心问题排查
你现有代码的核心错误有3个:
merge方法是非原地修改方法,你写的values[area.name].merge({axis.name=>{count: 0, total: 0.0}})不会修改原哈希,导致新增维度时初始化失效,后续访问对应属性会报空指针错误- 最终计算平均值的遍历逻辑层级错误,当前存储结构是
区域→维度→统计值,你原来的单层遍历无法匹配这个嵌套结构 - 未预加载关联数据存在严重的N+1查询问题,数据量稍大就会导致接口性能极差
修复后完整代码
控制器代码(增加预加载优化)
def index # 预加载关联数据彻底解决N+1查询问题 collection = Chart.includes(employee: :areas, :chart_axis_values).all render json: collection, meta: {averages: collection.averages } end
模型averages方法代码
## /models/chart.rb class Chart < ApplicationRecord belongs_to :employee has_many :chart_axis_values def self.averages # 用哈希默认Proc自动处理嵌套结构初始化,不需要手动判断空值 area_stats = Hash.new { |h, area| h[area] = Hash.new { |dh, dim| dh[dim] = {count: 0, total: 0.0} } } all.each do |chart| chart.chart_axis_values.each do |axis| # 跳过空值数据 next if axis.value.nil? chart.employee.areas.each do |area| # 直接累加计数和总和,初始化逻辑已经由哈希默认Proc处理 area_stats[area.name][axis.name][:count] += 1 area_stats[area.name][axis.name][:total] += axis.value end end end # 两层遍历计算每个区域下各维度的平均值 area_stats.each_with_object({}) do |(area_name, dim_stats), result| result[area_name] = dim_stats.each_with_object({}) do |(dim_name, stats), dim_result| dim_result[dim_name] = stats[:total] / stats[:count] end end end end
返回结果格式示例
{ "averages": { "华东区": { "motivation": 4.2, "satisfaction": 3.8 }, "华南区": { "motivation": 4.5, "satisfaction": 4.1 } } }
可选兼容方案
如果你需要同时支持全量平均值和按区域拆分平均值两种模式,可以给方法加可选参数:
def self.averages(group_by_area: false) # 不传参数默认返回原来的全量维度平均值 unless group_by_area values = all.reduce({}) do |values, chart| chart.chart_axis_values.each do |axis| next if axis.value.nil? values[axis.slug] ||= {count: 0, total: 0.0} values[axis.slug][:count] += 1 values[axis.slug][:total] += axis.value end values end return values.each_with_object({}) { |(axis, stats), res| res[axis] = stats[:total]/stats[:count] } end # 传group_by_area: true时返回按区域拆分的平均值 area_stats = Hash.new { |h, area| h[area] = Hash.new { |dh, dim| dh[dim] = {count: 0, total: 0.0} } } all.each do |chart| chart.chart_axis_values.each do |axis| next if axis.value.nil? chart.employee.areas.each do |area| area_stats[area.name][axis.name][:count] += 1 area_stats[area.name][axis.name][:total] += axis.value end end end area_stats.each_with_object({}) do |(area_name, dim_stats), result| result[area_name] = dim_stats.each_with_object({}) do |(dim_name, stats), dim_result| dim_result[dim_name] = stats[:total] / stats[:count] end end end
调用时按需求传参即可:collection.averages(group_by_area: true)
内容的提问来源于stack exchange,提问作者GuidoMedina
相关产品推荐
相关产品推荐

