Applying a different order to postgres "DISTINCT ON" using rails
In the user controller:
inner_query = current_user.tasks.next.to_sql
@projects = Task.paginate_by_sql("select * from (#{inner_query}) as user_projects order by user_projects.#{sort_column} #{sort_direction}", :page => params[:page])
And in the task model:
scope :next, select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC")
This way uses the power of postgres to only return the necessary records making the recordset smaller and easier to work with but the trade-off is that the RoR code doesn't look as inviting or as readable as it does with Carlos Drew's suggestion.
To answer this question:
I suppose my question is simply how do I wrap an activerecord query in a surrounding SELECT and ORDER B
Since ActiveRecord 4.0.2 there is now <model>.from
.
An example using your models:
inner_query = Project.joins(:tasks).select("DISTINCT ON (projects.id), *") // SELECT DISTINCT ON (projects.id) FROM projects INNER JOIN tasks ON tasks.project_id = projects.id;
You can wrap it in a from
:
sorted_query = Project.from(inner_query, :projects).order(:name)
You're trying to get a set of projects but you're starting with current_user.tasks
.
Why not start with current_user.projects
, which guarantees distinct projects?
@projects = current_user.projects.includes(:tasks).order("projects.name, tasks.milestone")
Alternative Answer
@projects = current_user.projects.joins(:tasks).select('projects.*, min(tasks.milestone) as next_milestone').order('projects.name').group('projects.id')
@projects.each{|p| puts "#{p.name} #{p.next_milestone}"}
That'll give you one row for each project, with a calculated minimum tasks.milestone value, accessible on the project row result via next_milestone. No extra tasks record, just the next milestone date.