【问题标题】:Rails Optimize query and loop through large entityRails 优化查询和循环遍历大型实体
【发布时间】:2017-08-12 15:35:20
【问题描述】:

我有一种方法可以输出以下用于图表的哈希格式。

# Monthly (Jan - Dec)
{
  "john": [1,2,3,4,5,6,7,8,9,10,11,12],
  "mike": [1,2,3,4,5,6,7,8,9,10,11,12],
  "rick": [1,2,3,4,5,6,7,8,9,10,11,12]
}
# the indices represents the month
# e.g [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
# Index
# 0 = Jan
# 1 = Feb
# 2 = Mar
...

以下方法遍历给定年份内具有特定销售代表名称的所有商店发票并生成上述结果

def chart_data
  hash = Hash.new {|h,k| h[k] = [] }

  (1..12).each do |month|
    date_range = "1/#{month}/#{date.year}".to_date.all_month
    all_reps.each do |name|
      hash[name] << store.bw_invoices.where(sales_rep_name: name, 
      purchase_date: date_range).sum(:subtotal).to_f
    end
  end
  return hash
end

当我运行这个方法时,它需要 4~5 秒才能执行。我真的需要优化这个查询。我想出了两个我认为会有所帮助的解决方案,但我很想获得您的一些专业知识。

  1. 将其移至后台作业
  2. 执行 SQL 查询以进行优化(如果这是最佳的,我需要帮助)

非常感谢您的宝贵时间

【问题讨论】:

  • 您还可以添加一些示例数据吗? Reps.all.first(10) 还有,date.year 是从哪里来的?
  • 模型之间是如何连接的? all_repsstorebw_invoices 是如何定义或来自的?​​span>
  • @spickermann 商店 has_many 发票,all_reps 是发票内的列。我只是采摘和 uniq 来获取所有代表。我希望这会有所帮助
  • @Vlad 感谢您的回答,date.year 可能只是 Date.today.year。如果我的代表次数超过 10 次,会发生什么?因为我需要遍历所有这些
  • 你在用postgresql吗?

标签: ruby-on-rails ruby postgresql loops ruby-on-rails-4


【解决方案1】:

是的,如果不让数据库进行艰苦的工作,您发现了一个很难有效解决的问题。

假设您的数据集可能太大而无法将一整年的原始数据加载到 ruby​​ 对象中,这种仅使用 1 个 postgreSQL 查询的方法可能是最好的想法:

更多 SQL 方法

def chart_data
  result = Hash.new {|h,k| h[k] = [] }

  total_lines = store.bw_invoices.select("sales_rep_name, to_char(purchase_date, 'mm') as month, sum(subtotal) as total")
                                 .where(purchase_date: Date.today.all_year)
                                 .group("sales_rep_name, to_char(purchase_date, 'mm')")                                 

  total_lines.each do |total_line|
    result[total_line.sales_rep_name][total_line.month.to_i - 1] = total_line.total.to_f
  end

  result
end

请注意,在代表没有销售的月份内,此解决方案将保留 nil 而不是 0。如果他们最后一个月的销售额是 6 月,那么数组中将只有 6 个项目。

我们可以通过从虚拟表中左连接更复杂的 SQL 或事后填充数组间隙来避免这种情况。但是,根据您设置图表的方式,这可能不会产生实际影响。

更多红宝石方法

def chart_data
  result = Hash.new {|h,k| h[k] = [] }

  (1..12).each do |month|
    date_range = "1/#{month}/#{Date.today.year}".to_date.all_month
    rows = store.bw_invoices.select("sales_rep_name, SUM(subtotal) as total")
                            .where(purchase_date: date_range)
                            .group(:sales_rep_name)

    all_reps.each do |rep_name|
      row = rows.detect { |x| x.sales_rep_name == rep_name }
      result[rep_name] << (row ? row.total : 0).to_f
    end
  end

  result
end

这与您的方法更相似,但将查询置于内部循环之外,因此我们执行 12 次查询而不是 12 * 代表次数。使用的detect 可能会变得有点慢,但前提是有数千个代表。在这种情况下,您可以同时对all_reps 和查询输出进行排序并实现您自己的merge join,但此时您会遇到复杂性,您不妨让数据库再次处理。

【讨论】:

  • 非常感谢您的反馈,我还没有机会测试该方法!我正要优化,看看它是否有效!再次感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-10
  • 2015-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多