ruby-on-rails - 如何(大量)减少 Rails 应用程序中的 SQL 查询数量?

标签 ruby-on-rails ruby ruby-on-rails-3 activerecord

在我的 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) 之前有更多的查询。

我认为主要问题之一是每张发票的subtotaltotal_taxtotal 没有存储在数据库中的任何位置,而是存储在数据库中计算每个请求。

谁能告诉我如何加快这里的速度?我不太熟悉 SQL 和 ActiveRecord 的内部工作原理,所以这可能是这里的问题。

感谢您的帮助。

最佳答案

每当调用 revenue_between 时,它都会获取给定时间范围内的payments 以及关联的invoicesitems 来自数据库。由于时间范围有很多重叠(月、季度、年),因此会一遍又一遍地获取相同的记录。

我觉得最好一次获取用户的所有付款,然后在 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/

相关文章:

ruby-on-rails - New Relic 导致未初始化的常量 Mongoid::Collection (NameError)

ruby-on-rails-3 - 如何在Railtie中使用thor代替rake

ruby-on-rails - Omniauth 错误 -- PG::Error: ERROR: 重复键值违规

ruby-on-rails-3 - 使用 Cucumber 测试未用 Rails 编写的 API

ruby-on-rails - Rails validates_with 错误 : :attributes cannot be blank

css - jquery-mobile 和全局 css 主题之间的 rails mobile view 颜色冲突

ruby - 如何访问 Jekyll 中另一个页面中的 YAML 信息

ruby-on-rails - 找不到 rvm 命令

html - 在 Controller 中生成下标并传递给 View

ruby - Rack::Auth 是否与基本 HTTP 身份验证相同?