Применение другого порядка для postgres "DISTINCT ON" с использованием рельсов
У меня есть приложение rails:
user has_many :projects
user has_many :tasks, :through => :projects
project has_many :tasks
Каждая задача имеет знаменательную дату.
Чтобы показать таблицу деталей проекта со следующей вехой, я использую:
@projects = current_user.tasks.joins(:project).select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC")
Это отлично работает.
Теперь я хочу иметь возможность сортировать столбцы таблицы.
В соответствии с Postgres DISTINCT ON
не сортируется, вы должны обернуть его в другой оператор select, то есть SELECT * FROM (SELECT DISTINCT ON....) ORDER BY column_3
Я думал, что упорядоченный столбец может быть просто обработан в SQL по мере необходимости, т.е. (для заказа по названию проекта DESC):
@projects = current_user.tasks.joins(:project).select("distinct on (projects.name) projects.*, tasks.*").reorder("projects.name DESC, tasks.milestone ASC")
который работает, но я также хочу иметь возможность заказать по веху, и это не работает.
Может ли кто-нибудь сказать мне, как преобразовать мой запрос на рельсы, чтобы он мог быть заказан любым из столбцов?
ОБНОВЛЕНИЕ
Я полагаю, что мой вопрос заключается в том, как просто обернуть запрос activerecord в окружении SELECT
и ORDER BY
?
Думаю, мне удалось добиться этого, используя:
inner_query = current_user.tasks.select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC").to_sql
@projects = Task.paginate_by_sql("select * from (#{inner_query}) as user_projects order by user_projects.name", :page => params[:page])
Это лучший способ, или кто-то может подумать о лучшем способе? - find/paginate_by_sql кажется обходным путем, и я предпочел бы оставаться в пределах запросов activerecord.
Спасибо
Ответы
Ответ 1
Вы пытаетесь получить набор проектов, но начинаете с current_user.tasks
.
Почему бы не начать с current_user.projects
, который гарантирует различные проекты?
@projects = current_user.projects.includes(:tasks).order("projects.name, tasks.milestone")
Альтернативный ответ
@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}"}
Это даст вам одну строку для каждого проекта с рассчитанным минимальным значением task.milestone, доступным в результате выполнения строки проекта через next_milestone. Никакой дополнительной записи задач, просто следующая веха.
Ответ 2
В пользовательском контроллере:
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])
И в модели задачи:
scope :next, select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC")
Этот способ использует силу postgres, чтобы возвращать только нужные записи, что делает набор записей меньшим и легче работать, но компромисс заключается в том, что код RoR не выглядит привлекательным или читаемым, как это происходит с Карлосом Дрю предложение.
Ответ 3
Чтобы ответить на этот вопрос:
Я предполагаю, что мой вопрос просто, как мне обернуть запрос activerecord в окружающий SELECT и ORDER B
Начиная с ActiveRecord 4.0.2 теперь есть <model>.from
.
Пример использования ваших моделей:
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;
Вы можете обернуть его from
:
sorted_query = Project.from(inner_query, :projects).order(:name)