Применение другого порядка для postgres "DISTINCT ON" с использованием рельсов - программирование
Подтвердить что ты не робот

Применение другого порядка для 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.

Спасибо

4b9b3361

Ответ 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)