Reputation: 1428
I have two Models invoice
and payments
. The relationship between them is invoice
has_many payments
.
I'm using the following left outer join to return all invoices that have not been paid at all:
result1 = Invoice.includes(:payments).where(:payments => { :id => nil })
I'm also interested in all invoices that have been partially paid. To return those I use an inner join:
result2 = Invoice.joins(:payments).group("transfers.id").having("sum(payments.amount) < invoice.amount")
I would now like to combine the two results, i.e. I want all Invoices that have either not been paid, or not been fully paid. I know that I could just do result = result1 + result2
. However, this doesn't return an ActiveRecord object. Is there a way to combine those two queries in a single query?
I use Rails 4.1 and PostgreSQL
Upvotes: 2
Views: 2431
Reputation: 1428
I ended up doing just a LEFT OUTER JOIN. However, Rails doesn't seem to support group
and having
with the LEFT OUTER JOIN generated by includes()
. Therefore, i had to construct the join manually which actually doesn't feel like "the Rails way".
My query:
Invoice.joins("LEFT JOIN payments ON payments.transfer_id = invoices.id").group("invoices.id, payments.id").having("((payments.id IS NULL) OR (sum(payments.amount) < invoices.amount))")
Update: This doesn't work as expected. Please see the accepted answer.
Upvotes: 0
Reputation: 1985
I believe you're correct that you can't get ActiveRecord to generate anything other than an inner join without writing it yourself. But I wouldn't use includes
in this context, for while it does happen to cause ActiveRecord to generate a different join, that is an "implementation detail" -- it's fundamental purpose is to load the associated models, which is not necessarily what you want.
In your proposed solution, I don't understand why you'd group by both invoices.id
and payments.id
-- that seems to defeat the purpose of grouping.
You could do something like the following, although I can't say that this seems much more Rails-ish.
Invoice.joins("LEFT JOIN payments ON payments.transfer_id = invoices.id")
.select("invoices.id, SUM(payments.amount) AS total_paid")
.group("invoices.id")
.having("SUM(payments.amount) IS NULL OR SUM(payments.amount) < invoices.amount")
This will return a list of Invoice objects with only the id
field and total_paid
field set. If you need other fields available, add them to both the select
statement and the the group
statement.
Upvotes: 3