Skip to content

Instantly share code, notes, and snippets.

@stevenharman
Created January 21, 2013 20:44
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save stevenharman/4589186 to your computer and use it in GitHub Desktop.
Save stevenharman/4589186 to your computer and use it in GitHub Desktop.
Using Arel/ActiveRecord to execute a subquery, including a SUM.
select fund_sums.total, fund_products.name
from (
select sum(allocation_amount) total, fund_product_id
from transactions
where transactions.investor_id = 490
group by fund_product_id
) fund_sums
left join fund_products on fund_sums.fund_product_id = fund_products.id
@danshultz
Copy link

Transaction.where(:investor_id => 490).joins(:fund_products).sum(:allocation_amount, :group_by => FundProduct.arel_table[:name])

Hows that work?

@stevenharman
Copy link
Author

After reading the Arel source code and tests, this will work.

tt = Transaction.arel_table
fpt = FundProduct.arel_table

fund_sums = tt.project(tt[:allocation_amount].sum.as('total'), tt[:fund_product_id])
fund_sums.from(tt)
fund_sums.where(tt[:investor_id].eq(490))
fund_sums.group(tt[:fund_product_id])
fund_sums = fund_sums.as(Arel.sql('fund_sums'))

manager = Arel::SelectManager.new(tt.engine)
manager.project(fund_sums[:total], fpt[:name])
manager.from(fund_sums)
manager.join(fpt).on(fpt[:id].eq(fund_sums[:fund_product_id]))

ActiveRecord::Base.connection.execute(manager.to_sql) #=> An array-like object, full of hashes

#=> [
#       { 'name' => 'some fund name', 'total' => 1000.00 }
#       ...
#     ]

@stevenharman
Copy link
Author

@danshultz Close, but FundProduct#name is not unique, hence the need to group by the #id.

@danshultz
Copy link

You can also do this if that causes problems with duplicate names, alternatively, doing a group by like this works but you get an array as your "key" in the ActiveSupport::OrderedHash

fund_product = FundProduct.arel_table

Transaction.where(:investor_id => 490).joins(:fund_products).sum(:allocation_amount, :group_by => [fund_product[:id], fund_product[:name]])

If you want to do it differently, you can do it with more arel but I think the above should cover you

@danshultz
Copy link

You could cheat a little more with your example

tt = Transaction.arel_table
fpt = FundProduct.arel_table

fund_sums = Transaction.select(tt[:allocation_amount].sum.as('total'), tt[:fund_product_id])).where(:investor_id => 490).group(:fund_product_id).arel.as(Arel.sql("fund_sums")

@joshskeen
Copy link

Transaction.where(:investor_id => 490).joins(:fund_product).sum(:allocation_amount, :group => [Transaction.arel_table[:fund_product_id], fund_product[:name]])

@martinstreicher
Copy link

@stevenharman Thanks for this gist. It helped a lot. What is the magic of this:

manager = Arel::SelectManager.new(tt.engine)

Why .engine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment