2012-04-19 21 views
0

多くのデータを取り込み、計算を実行し、大きなテーブルの一部として吐き出すレポートを生成する必要があります。これを行うことは困難ではない。しかし、既存のメソッドを使用できるようにして、1000のSQLクエリを生成しないようにするのはもっと難しいです。これは、月の初めに言って口座残高を取得するために使用することができActiveRecordを使用して効率的にレポートを生成する

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残高のテーブルをしたい場合。だから、例えば:(

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はデータをグループ化して集約するために設計されており、ルビーよりもこのような処理に対応しています。ただし、ほとんどの場合、ビューはオンザフライで実行されるため、ここでパフォーマンスが向上しているようなわけではありません。理想的には、基本的な実装を得たら、cronタスクやデータを事前に計算する手段を設定することができます。レポートに頻繁にアクセスしたり、日中にアクセスする場合は、専用のレポートDBが必要になります。レポートにライブデータが必要な場合は、レプリケーションを設定する必要があります。

Ruby/Rails内でSQLを混乱させるのは面倒で、私は知っています。だから私はSkiimaと呼ばれる宝石を書いており、あなたがあなたのプロジェクトで持つかもしれない無関係な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

あなたの最善の策、あなたは、単にfind_by_sqlを使用している(他のツールとは対照的に)Railsの内側に滞在していると仮定すると()。

確かに醜いですが、それはREADABLEになります。これは、生のSQLよりも醜いものではありません。

私は「Rubyでの計算は、」特別レポートのためにはるかに多くのパフォーマンスfind_by_sqlに置き換えたのRailsアプリの数に働いてきました。それはいつも少し汚れているように感じますが、私はまた、5mのレポートを取って、まともなSQLを使って30秒間実行するのが好きです。

+0

をX、Y、Zを使用すると、それらのスコープを単純に連鎖させることができます。複雑なクエリが相互に干渉し始める特定のポイントにヒットするかどうかはわかりません。 おそらく、レポートの各列は 'find_by_sql'またはスコープに関連しているため、簡単にテストすることができ、列ごとのクエリの価格に対して競合がないことを保証できます。 – Theozaurus

1

あなたが1000sのアカウントを持っていると仮定すると、私の最初の質問は本当にすべてを一度に表示する必要がありますか?これは本当にユーザーにとって便利ですか?

そうでない場合は、最初の方法を続行できます。ページあたりのアカウント数を許容レベルに制限してください。関数呼び出しごとに2つのクエリを実行しますが、テスト可能で信頼性の高いクエリです。レポートにページをレンダリングしている場合は

は、それは少し時間がかかることがあり、ユーザーに簡単な説明しますprintout-。

私はあなたのより速い解決策の必要性を理解していますが、時には速くても必ずしもユーザーフレンドリーではありません。

+0

悲しいことに、それが必要です。ユースケースの種類は、会計パッケージにレポートをダウンロードすることです。 – Theozaurus

+0

バックグラウンドスレッドでレポート生成を実行し、その完了をユーザーに通知する可能性はありますか?大規模なデータサンプルの実行には現在どのくらい時間がかかりますか? – apchester

+0

はい、可能です。私は主に効率の観点からこの問題を解決する方法に関心を持っていますが、複雑さを最小限に抑えています。 – Theozaurus

関連する問題