2017-06-15 47 views
1

我有這種情況。提高性能:避免在集合中尋找正確的元素

activity.rb

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 

我有一個很大的報告,我取得了良好的性能(SQL查詢的數量是固定的),但我認爲我可以做得更好。我使用的查詢是

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

這是確定的,這是快,但我認爲這是改善的,因爲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

您可以考慮使用ArelArel是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) 

這將添加請求加入例如

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]) 

更新

如果你只是想添加「hourly_cost」這應該爲你工作

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看起來像

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]) 

你可以在Arel構建選擇部分太多,如果你喜歡,但矯枉過正,似乎對於這樣一個簡單的語句。

+0

男人,查詢工作,但它也是我的工作。問題是呼叫'hourly_cost_by_year'在這一行'duration/60.0 * user.hourly_cost_by_year(date.year).amount rescue 0' – Ursus

+0

我的意思是,我不知道如何指向正確的hourly_cost而不使用hourly_costs查找 – Ursus

+0

@Ursus用最簡單的術語解釋你想要的結果,我很樂意幫你建立一個查詢來獲得它。用戶是您想要定位的頂級表格嗎?如果需要,您可以發佈所需的Sql。如果它是有效的Arel可以構建它 – engineersmnky