Rails 3: .group () упорядочено созданным - PullRequest
13 голосов
/ 04 ноября 2011

Как в Rails 3 упорядочить результат .group (), который приводит к Activerecord (здесь «made_at»)?

@messages = Message.group(:foo)

приводит только к отображению самого старого сообщения. Мне нужно, чтобы последний показывали.

Я пытался

@messages = Message.group(:foo).having("created_at = MAX(created_at)")

безуспешно. Любые советы приветствуются!

Чтобы уточнить: я смотрю, чтобы группа была упорядочена внутри себя, а не обычный messages.order ("..."). Если бы не было легкого синтаксиса Activerecord, я был бы рад и сырому SQL


Обновление: пробуя способ SQL, это должно было работать:

@messages = Message.find_by_sql("
  SELECT messages.* 
  FROM messages 
  GROUP BY messages.foo 
  HAVING messages.created_at = MAX(messages.created_at) 
  ORDER BY messages.created_at DESC")

Но при этом извлекаются только отдельные записи (те, которые не сгруппированы). Предположительно, сгруппированные опущены. Не знаю почему, все записи имеют значения: create_at и: foo

Ответы [ 5 ]

6 голосов
/ 30 марта 2012

Вы не можете использовать порядок, а затем группировать в одном запросе в некоторых базах данных, поэтому вам нужно сделать это с помощью двух запросов:

message_ids = Message.select("MAX(id) AS id").group(:foo).collect(&:id)
@messages = Message.order("created_at DESC").where(:id => message_ids)

Примечание. Предполагается, что у вас есть столбец id с автоинкрементом, который есть в большинстве таблиц Rails.

2 голосов
/ 09 мая 2014

Вы можете использовать SQL, но вы также можете в основном оставаться на земле активной записи с

@messages = Message.select('DISTINCT ON (foo) *').order(:created_at).reverse_order

Синтаксис DISTINCT ON () - это postgres. В MYSQL похожий, но немного другой синтаксис.

Я думаю, что этот запрос также окажется намного более эффективным, чем принятый ответ, потому что это всего лишь одна сортировка и сканирование вместо N сортировок.

1 голос
/ 04 августа 2014

Я только что столкнулся с этой же проблемой. Следующий запрос Rails работал для меня:

Message.where("id IN (SELECT MAX(id) FROM messages GROUP BY id) AND state = 'unread'")

Здесь мы используем подзапрос, чтобы получить самый большой (и, следовательно, самый последний) идентификатор в каждой группе, а затем отфильтровать их, чтобы показать только те, где состояние == 'непрочитано'.

Совет: я создал метод self.latest в своем классе модели сообщений, который состоял из:

def self.latest
  where("id IN (SELECT MAX(id) FROM messages GROUP BY id)
end

Что означало, что я мог бы использовать его в контроллерах следующим образом:

Message.latest.where(state: 'unread')
1 голос
/ 13 ноября 2011

Все работает, используя более специализированный подзапрос, без вызова GROUP BY:

SELECT *
   FROM `messages`
   WHERE `id` = (
      SELECT `id`
      FROM `messages` as `alt`
      WHERE `alt`.`foo` = `messages`.`foo`
      ORDER BY `created_at` DESC
      LIMIT 1
   )
   ORDER BY `created_at` DESC

Все благодаря ответу Хаоса в этой теме: Выберите 3 последние записи, в которых значения одного столбца различны

Пока не знаю о каких-либо последствиях для скорости. Если кто-то хочет вмешаться, пожалуйста, не стесняйтесь.

0 голосов
/ 07 ноября 2011

Я не понимаю, как это было бы легко сделать с использованием Arel, но если вы просто создадите свой собственный SQL, как вы делали это в своем вопросе, то измените его следующим образом:

@messages = Message.find_by_sql("
  SELECT messages.* 
  FROM messages 
  GROUP BY messages.foo 
  HAVING messages.created_at = (SELECT max(created_at) FROM messages AS sub_messages WHERE sub_messages.foo = messages.foo)

Обратите внимание на подзапрос в предложении HAVING. Ваше предложение HAVING рассчитывает МАКС для всех ваших сообщений, а не только для сообщений в foo. Подзапрос должен решить эту проблему.

Я связал имя таблицы сообщений с sub_messages - но это не обязательно. Я просто сделал это, чтобы «показать», как родитель и подзапрос работают вместе.

...