我有这种情况。
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_costs之间直接选择我想要的hourly_cost而不进行搜索。这可能吗?
答案 0 :(得分:2)
您可以考虑使用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)
这将添加请求的加入,例如
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])
<强>更新强>
如果您只想添加&#34; hourly_cost&#34;这应该对你有用
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
中构建选择部分,但对于这样一个简单的陈述来说似乎有些过分。