简体   繁体   中英

How to add sum of a field from joined model in Rails?

How to add sum of a field from joined model using Rails 3.2 and MySql 5.5 ?

Let's say I have models like this:

class Account < ActiveRecord::Base
  attr_accessible :number
  has_many :operations
end

class Operation < ActiveRecord::Base
  belongs_to :account
  attr_accessible :op_type,  # either 'deposit' or 'withdrawal'
                  :amount
end

I need to select accounts using some condition and add to each of them sum of all deposits for the account.

This can be done with SQL like this:

SELECT *,
    IFNULL((
        SELECT SUM(amount)
        FROM operations
        WHERE operations.account_id = accounts.id AND operations.op_type = 'deposit'
    ), 0) as total_deposits
FROM accounts
WHERE <condition for accounts>

(Using LEFT JOIN is another way to achieve the same result.)

How can I do that with Rails?

I want something like this:

accounts = Account.where(<mycondition>). join(???). sum(???)  # What should be here?
accounts.each do |a|
  puts "Account #{a.number} has deposited #{a.total_deposits} total."
end

Try Operation.joins(:account).where(<mycondition>).sum(:amount)

The field being summed amount is in operations table; so the active record query would be on the Operation model as well. The mycondition should be defined to include operations belonging to the specific account.

If you need to do a LEFT JOIN to retrieve accounts even when they have no operation records, you'll need to type out that join condition with something like:

totals = Account.where(<account conditions>).joins("LEFT JOIN operations ON operations.account_id = accounts.id AND operations.op_type = 'deposit'").group("accounts.number").sum(:amount)
totals.each do |a|
  puts "Account #{a[0]} has deposited #{a[1]} total."
end

And if you're willing to split this into two queries, this is an option:

accounts = Account.where(<account conditions>)
totals = Operation.where(op_type: "deposit", account_id: accounts.map(&:id)).group(:account_id).sum(:amount)
accounts.each do |a|
  puts "Account #{a.number} has deposited #{totals[a.id] || 0} total."
end

Edit: If you need account instances and need to sort by the sums, some additional SQL will start to creep in. But something like this should work:

accounts = Account.where(<account conditions>).joins("LEFT JOIN operations ON operations.account_id = accounts.id AND operations.op_type = 'deposit'").group("accounts.number").select("accounts.*, COALESCE(SUM(amount), 0) AS acct_total").order("acct_total")
accounts.each do |a|
  puts "Account #{a.number} has deposited #{a.acct_total} total."
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM