2017-06-15 49 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