For these kinds of things, I find it helpful to get the raw SQL working first, and then translate it into ActiveRecord afterwards. It sounds like a textbook case of GROUP BY
:
SELECT fiscal_year_end, MAX(date) AS max_date
FROM statements
WHERE company_id = 1
GROUP BY fiscal_year_end
Now you can express that in ActiveRecord like so:
c = Company.first
c.statements.
group(:fiscal_year_end).
order(nil). # might not be necessary, depending on your association and Rails version
select("fiscal_year_end, MAX(date) AS max_date")
The reason for order(nil)
is to prevent ActiveRecord from adding ORDER BY id
to the query. Rails 4+ does this automatically. Since you aren't grouping by id
, it will cause the error you're seeing. You could also order(:fiscal_year_end)
if that is what you want.
That will give you a bunch of Statement
objects. They will be read-only, and every attribute will be nil
except for fiscal_year_end
and the magically-present new field max_date
. These instances don't represent specific statements, but statement "groups" from your query. So you can do something like this:
- @statements_by_fiscal_year_end.each do |s|
%tr
%td= s.fiscal_year_end
%td= s.max_date
Note there is no n+1 query problem here, because you fetched everything you need in one query.
If you decide that you need more than just the max date, e.g. you want the whole statement with the latest date, then you should look at your options for the greatest n per group problem. For raw SQL I like LATERAL JOIN
, but the easiest approach to use with ActiveRecord is DISTINCT ON
.
Oh one more tip: For debugging weird errors, I find it helpful to confirm what SQL ActiveRecord is trying to use. You can use to_sql
to get that:
c = Company.first
puts c.statements.
group(:fiscal_year_end).
select("fiscal_year_end, MAX(date) AS max_date").
to_sql
In that example, I'm leaving off order(nil)
so you can see that ActiveRecord is adding an ORDER BY
clause you don't want.