2017-06-15 3 views
1

私はこのような状況に陥っています。パフォーマンスを向上させる:コレクション内の適切な要素を探すのを避けてください。

belongs_to :user 
belongs_to :cause 
belongs_to :sub_cause 
belongs_to :client 

def amount 
    duration/60.0 * user.hourly_cost_by_year(date.year).amount rescue 0 
end 

user.rb

has_many :hourly_costs # one hourly_cost for year 
has_many :activities 

def hourly_cost_by_year(year = Date.today.year) 
    hourly_costs.find { |hc| hc.year == year } 
end 

hourly_cost.rb

belongs_to :user 

activity.rb

私は良好な性能を達成し、大きなレポート(SQLクエリの数を持っています固定されていますが)私はもっとうまくいくと思います。私が使用してクエリが

activities = Activity.includes(:client, :cause, :sub_cause, user: :hourly_costs) 

であり、これはokです、それは高速ですが、私は理由hourly_cost_by_year方法の改善可能だと思います。つまり、アクティビティには日付があり、その日付を使用して、時間当たりの費用のうちどれを使用すべきかを知ることができます。 activity

def self.user_with_single_hourly_cost 
    joins('LEFT JOIN users u ON u.id = activities.user_id'). 
    joins('LEFT JOIN hourly_costs hc ON hc.user_id = u.id AND hc.year = EXTRACT(year from activities.date)') 
end 

でこのような何かしかし、私はどのように私のクエリでこれを統合していません。私が何を試してもうまくいかなかった。私は生のSQLを使用することができますが、ActiveRecordを使用しようとしています。私はredisを使用してユーザーと年ごとに毎時のキャッシュをキャッシュすると考えていましたが、動作する可能性がありますが、フラットテーブルを使用するため、抽出部分を使ってこのクエリを実行してください。

更新:私は明確にしようとします。私はいくつかの点で私のアクションで使うものは何でも、クエリ私は

activities.sum(&:amount) 

及びその方法をしなければならない、あなたが知っている、

def amount 
    duration/60.0 * user.hourly_cost_by_year(date.year).amount rescue 0 
end 

であると私は私が望む直接hourly_costを選択する方法がわかりませんhourly_costsの間の検索なし。これは可能ですか?

答えて

1

これにはArelを使用することをお勧めします。 Arelは、rails/activerecord(新しい依存関係はありません)の基礎となるクエリアセンブラであり、複雑なクエリを構築する際に非常に便利です。これは、上位レベルActiveRecord::QueryMethodsよりはるかに深いものです。

明らかに、より広範なAPIでは、冗長性(実際にはかなり読みやすくなります)と文法的な砂糖は少なくなりますが、これは慣れていますが、私にとっては不可欠なことです。

私はあなたのデータ構造を再作成するには時間がかかることはありませんでしたが、このような何かがあなた

activities = Activity.arel_table 
users = User.arel_table 
hourly_costs = HourlyCost.arel_table 

activity_users_hourly_cost = activities 
    .join(users,Arel::Nodes::OuterJoin) 
    .on(activities[:user_id].eq(users[:id])) 
    .join(hourly_costs,Arel::Nodes::OuterJoin) 
    .on(hourly_costs[:user_id].eq(users[:id]) 
     .and(hourly_costs[:year].eq(Arel::Nodes::Extract.new(activities[:date],'year')) 
    ) 
) 
Activity.includes(:client, :cause, :sub_cause).joins(activity_users_hourly_cost.join_sources) 

のために働くことがありますが、これは、要求が追加されます例えば参加あなただけの「hourly_cost」を追加したい場合は

activity_users_hourly_cost.to_sql 
#=> SELECT 
    FROM [activities] 
    LEFT OUTER JOIN [users] ON [activities].[user_id] = [users].[id] 
    LEFT OUTER JOIN [hourly_costs] ON [hourly_costs].[user_id] = [users].[id] 
     AND [hourly_costs].[year] = EXTRACT(YEAR FROM [activities].[date]) 

更新

あなたは

Activity.includes(:client, :cause, :sub_cause) 
    .joins(activity_users_hourly_cost.join_sources) 
    .select("activities.*, activities.duration/60.0 * ISNULL([hourly_costs].[amount],0) as hourly_cost_by_year") 

これが唯一のActivityオブジェクトを返しますので、予めご了承くださいにとって、これは動作するはずですが、彼らは今持っているだろうhourly_cost_by_yearというメソッドは、その計算の結果を返します。完全なSQLは、あなたが好きな場合は、あまりにもArelで選択部分を構築することができ

SELECT 
    [activities].*, 
    activities.duration/60.0 * ISNULL([hourly_costs].[amount],0) as hourly_cost_by_year 
    FROM [activities] 
    -- Dependant upon WHERE Clause 
    LEFT OUTER JOIN causes ON [activities].[cause_id] = [causes].[id] 
    LEFT OUTER JOIN sub_causes ON [activities].[subcause_id] = [subcauses].[id] 
    LEFT OUTER JOIN clients [activities].[client_id] = [clients].[id] 
    -- 
    LEFT OUTER JOIN [users] ON [activities].[user_id] = [users].[id] 
    LEFT OUTER JOIN [hourly_costs] ON [hourly_costs].[user_id] = [users].[id] 
     AND [hourly_costs].[year] = EXTRACT(YEAR FROM [activities].[date]) 

のように見えるが、このような単純な文に行き過ぎと思われるでしょう。

+0

男は、クエリが動作しますが、それは私のも働いた。この行の 'hourly_cost_by_year'の呼び出しは' duration/60.0 * user.hourly_cost_by_year(date.year).amountレスキュー0' – Ursus

+0

です。私は、hourly_costsを使用せずに正しいhourly_costを指す方法を知りません – Ursus

+0

@Ursus簡単な言葉であなたが望むものを説明してください。私はあなたがそれを得るために質問を作成するのを助けてくれることを嬉しく思っています。ユーザーはターゲットとするトップレベルのテーブルですか?必要に応じて、必要なSqlを投稿することができます。それが有効であれば、Arelはそれを構築することができます – engineersmnky

関連する問題