使用ActiveRecord高效生成报告

时间:2012-04-19 17:25:03

标签: ruby-on-rails activerecord

我们需要生成报告,这些报告会提取大量数据,运行一些计算并将它们作为较大表的一部分进行吐出。这样做并不困难。但是,使它可以使用现有方法并且不生成1000个SQL查询要困难得多。

例如,我可能有一个Account类,其方法如下:

def balance_at(time=Time.now)
  payments_out = self.payments.where("created_at <= ?",time).sum("amount")
  payments_in  = self.payments_on_account.where("created_at <= ?",time).sum("amount")
  payments_in - payments_out
end

这可用于在月初和最后获得帐户余额。它很棒。

但是,如果我想要一个包含月末开始和结束的所有Account余额的表格,那么事情就会变得愚蠢。例如:

Account.includes(:payments, :payments_on_account)

如果我想完全用Ruby来解决这个问题,那么我将获得所需的所有数据,但是我很好的小方法balance_at并没有完成Ruby中的所有数字运算(这会很慢)个别情况)。

我可以用Ruby和SQL中的内容来解决它,具体取决于缓存的内容:

def balance_at(time=Time.now)
  payments_out, payments_in = [payments, payments_on_account].map{|payments|
    if payments.loaded?
      payments.find_all{|p| p.created_at < time }.inject(0){|a,p| p.amount + a }
    else
      payments.where("created_at <= ?",time).sum("amount")
    end
  }
  payments_in - payments_out
end

然而,这也不是可读或易于测试。

你会如何解决它?

3 个答案:

答案 0 :(得分:1)

假设你有1000个帐户,我的第一个问题是你真的需要一次显示它们吗?这对用户真的有用吗?

如果没有,那么您可以继续使用第一种方法 - 只需将每页的帐户数量限制在可接受的水平。每个函数调用仍然会执行两个查询,但它可测试且可靠。

如果您要为报表打印输出页面,那么向用户简单解释可能需要一些时间。

我理解您需要更快的解决方案,但有时更快并不一定更加用户友好。

答案 1 :(得分:1)

我参与了一些需要报告的项目。 Web应用程序堆栈不是进行报告的最佳位置,但似乎开源报告选项相当有限。但并非每个组织都可以使用SSRS或Crystal,根据我的经验,这些产品很痛苦,并且引入的问题多于必要的。

我正在使用视图来完成这些工作。 SQL专为分组和聚合数据而设计,它比ruby更能处理这些内容。但是,大多数情况下,视图将在运行中执行,所以这并不像你在这里获得性能提升。理想情况下,在获得基本实现后,您可以设置一些cron任务或预先计算数据的方法。如果要经常访问您的报告,并且在白天,您将需要一个专用的报告数据库。如果报告必须有实时数据,则需要设置复制。

我知道,在Ruby / Rails中弄乱SQL很麻烦而且不好意思。所以我编写了一个名为Skiima的gem,它可以帮助您管理项目中可能存在的无关SQL对象。通过加载迁移,可以更轻松地对其进行测试。

http://github.com/dcunited001/skiima

除此之外,这就是我一直在做的事情:

class AccountsReport < ActiveModel
  attr_accessor :items
  def initialize(attr = {})
    # read in params, set attrs
  end

  def execute
    get_report_items
    group_report_items
    summarize_report_groups # if this needs to occur outside of sql
  end
end

class AccoutsReportItem < ActiveRecord::Base
  # you can hook into a view here, you will want the view to return an id col
  set_table_name :view_accounts_report_items
end

# yay for arel and activerecord methods.  
# you can even set up relationships on these.  use sparingly.
# AccountsReportItem.where(:blah => 'balah')

答案 2 :(得分:0)

你最好的选择,假设你留在Rails中(而不是另一个工具)只是使用find_by_sql()。

它肯定会变得丑陋,但它将是 READABLE - 并且不会比原始SQL更难看。

我参与了许多Rails应用程序,其中“Ruby中的计算”被更高效的find_by_sql替换为报告。它总是感觉有点脏,但我也喜欢拿着5米的报告,让它们在30秒内以一些不错的SQL运行。