2012-04-19 49 views
0

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

比如我可能有一个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 

然而,这并不可怕可读或容易测试两种。

你会如何解决它?

回答

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') 
0

最好的办法是,假设你留在Rails中(而不是其他工具),只需使用find_by_sql()即可。

它肯定会很丑,但它会是可读 - 没有比原始SQL更丑陋的了。

我已经开发了大量Rails应用程序,其中“Ruby中的计算”被具有更高性能的find_by_sql替代,专门用于报告。它总是觉得有点肮脏,但我也喜欢拿出5米的报告,并用一些体面的SQL让它们在30秒内运行。

+0

我不知道是否有可能把每种类型的计算成一个范围,然后当你想用一个报告X,Y,Z在这里你只需将这些范围链接在一起。我不知道你是否会遇到某种情况,通过复杂的查询会开始互相干扰。 也许对于报告中的每一列涉及'find_by_sql'或作用域,然后您可以轻松地进行测试,并确保每列查询的价格无冲突。 – Theozaurus 2012-04-19 18:04:11

1

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

如果不是,那么您可以继续使用第一种方法 - 只是将每页帐户数限制在可接受的水平。你仍然会为每个函数调用做两个查询,但它的可测试性和可靠性。

在您为报表打印输出页面的情况下,然后向用户简单解释它可能需要一些时间。

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

+0

可悲的是它是必需的。用例的种类是将报告下载到会计软件包。 – Theozaurus 2012-04-19 18:01:10

+0

是否有可能在后台线程中运行报告生成,并通知用户其完成?目前需要多长时间处理大量数据样本? – apchester 2012-04-19 18:07:41

+0

是的,这些都是可能的。我主要关注如何从纯粹的效率角度解决这个问题,同时增加最小的复杂性。 – Theozaurus 2012-04-19 18:09:49