在我的 Rails 应用程序中,我有 users
,它可以有许多 invoices
,而 invoices
又可以有许多 payments
。
现在在 dashboard
View 中,我想总结一个 user
曾经收到的所有 payments
,按年、季度或月。 付款
也分割为毛额、 Netty 和税额。
user.rb:
class User < ActiveRecord::Base
has_many :invoices
has_many :payments
def years
(first_year..current_year).to_a.reverse
end
def year_ranges
years.map { |y| Date.new(y,1,1)..Date.new(y,-1,-1) }
end
def quarter_ranges
...
end
def month_ranges
...
end
def revenue_between(range, kind)
payments_with_invoice ||= payments.includes(:invoice => :items).all
payments_with_invoice.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount")
end
end
发票.rb:
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items
has_many :payments
def total
items.sum(&:total)
end
def subtotal
items.sum(&:subtotal)
end
def total_tax
items.sum(&:total_tax)
end
end
payment.rb:
class Payment < ActiveRecord::Base
belongs_to :user
belongs_to :invoice
def percent_of_invoice_total
(100 / (invoice.total / amount.to_d)).abs.round(2)
end
def net_amount
invoice.subtotal * percent_of_invoice_total / 100
end
def taxable_amount
invoice.total_tax * percent_of_invoice_total / 100
end
def gross_amount
invoice.total * percent_of_invoice_total / 100
end
end
dashboards_controller:
class DashboardsController < ApplicationController
def index
if %w[year quarter month].include?(params[:by])
range = params[:by]
else
range = "year"
end
@ranges = @user.send("#{range}_ranges")
end
end
index.html.erb:
<% @ranges.each do |range| %>
<%= render :partial => 'range', :object => range %>
<% end %>
_range.html.erb:
<%= @user.revenue_between(range, :gross) %>
<%= @user.revenue_between(range, :taxable) %>
<%= @user.revenue_between(range, :net) %>
现在的问题是这种方法有效,但也会产生大量的 SQL 查询。在典型的 dashboard
View 中,我得到了 100+ 个 SQL 查询。在添加 .includes(:invoice)
之前有更多的查询。
我认为主要问题之一是每张发票的subtotal
、total_tax
和total
没有存储在数据库中的任何位置,而是存储在数据库中计算每个请求。
谁能告诉我如何加快这里的速度?我不太熟悉 SQL 和 ActiveRecord 的内部工作原理,所以这可能是这里的问题。
感谢您的帮助。
最佳答案
每当调用 revenue_between
时,它都会获取给定时间范围内的payments
以及关联的invoices
和items
来自数据库。由于时间范围有很多重叠(月、季度、年),因此会一遍又一遍地获取相同的记录。
我觉得最好一次获取用户的所有付款,然后在 Ruby 中过滤和汇总它们。
要实现,请按如下方式更改revenue_between
方法:
def revenue_between(range, kind)
#store the all the payments as instance variable to avoid duplicate queries
@payments_with_invoice ||= payments.includes(:invoice => :items).all
@payments_with_invoice.select{|x| range.cover? x.created_at}.sum(&:"#{kind}_amount")
end
这会立即加载所有付款以及相关的发票和项目。
同时更改 invoice
求和方法,使其使用预先加载的 items
class Invoice < ActiveRecord::Base
def total
items.map(&:total).sum
end
def subtotal
items.map(&:subtotal).sum
end
def total_tax
items.map(&:total_tax).sum
end
end
关于ruby-on-rails - 如何(大量)减少 Rails 应用程序中的 SQL 查询数量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19077000/