Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to (massively) reduce the number of SQL queries in Rails app?
    primarykey
    data
    text
    <p>In my Rails app I have <code>users</code> which can have many <code>invoices</code> which in turn can have many <code>payments</code>.</p> <p>Now in the <code>dashboard</code> view I want to summarize all the <code>payments</code> a <code>user</code> has ever received, ordered either by year, quarter, or month. The <code>payments</code> are also subdivided into <em>gross</em>, <em>net</em>, and <em>tax</em>.</p> <p><strong>user.rb</strong>:</p> <pre><code>class User &lt; 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 =&gt; :items).all payments_with_invoice.select { |x| range.cover? x.date }.sum(&amp;:"#{kind}_amount") end end </code></pre> <p><strong>invoice.rb</strong>:</p> <pre><code>class Invoice &lt; ActiveRecord::Base belongs_to :user has_many :items has_many :payments def total items.sum(&amp;:total) end def subtotal items.sum(&amp;:subtotal) end def total_tax items.sum(&amp;:total_tax) end end </code></pre> <p><strong>payment.rb</strong>:</p> <pre><code>class Payment &lt; 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 </code></pre> <p><strong>dashboards_controller</strong>:</p> <pre><code>class DashboardsController &lt; 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 </code></pre> <p><strong>index.html.erb</strong>:</p> <pre><code>&lt;% @ranges.each do |range| %&gt; &lt;%= render :partial =&gt; 'range', :object =&gt; range %&gt; &lt;% end %&gt; </code></pre> <p><strong>_range.html.erb</strong>:</p> <pre><code>&lt;%= @user.revenue_between(range, :gross) %&gt; &lt;%= @user.revenue_between(range, :taxable) %&gt; &lt;%= @user.revenue_between(range, :net) %&gt; </code></pre> <p>Now the problem is that this approach works but produces an awful lot of SQL queries as well. In a typical <code>dashboard</code> view I get <strong>100+</strong> SQL queries. Before adding <code>.includes(:invoice)</code> there were even more queries.</p> <p>I assume one of the major problems is that each invoice's <code>subtotal</code>, <code>total_tax</code> and <code>total</code> aren't stored anywhere in the database but instead calculated with every request.</p> <p>Can anybody tell me how to speed up things here? I am not too familiar with SQL and the inner workings of ActiveRecord, so that's probably the problem here.</p> <p>Thanks for any help.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload