【问题标题】:Increase performance: avoid looking for the right element in a collection提高性能:避免在集合中寻找正确的元素
【发布时间】:2017-06-15 15:33:38
【问题描述】:

我有这种情况。

活动.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

用户.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 之间进行搜索。这可能吗?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案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 中构建选择部分,但对于这样一个简单的语句来说似乎有点过分了。

    【讨论】:

    • 伙计,查询有效,但我的也有效。问题是在这一行duration / 60.0 * user.hourly_cost_by_year(date.year).amount rescue 0 中调用hourly_cost_by_year
    • 我的意思是,我不知道如何在不使用 find on hourly_costs 的情况下指向正确的 hourly_cost
    • @Ursus 用最简单的方式解释你想要的结果,我很乐意帮助你建立一个查询来得到它。用户是您要定位的顶级表吗?如果需要,您可以发布所需的 Sql。如果它是有效的,Arel 可以构建它
    • @Ursus 是您关心“金额”的唯一结果,或者您是否需要查询本身的其他信息。例如活动、用户、客户端等?
    • 金额是我构建报告时唯一需要的数据。然后,我还必须返回用户、客户等,但它们不需要详细说明
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多