Как сортировать Rails AR.find по количеству объектов в отношениях has_many
Как я могу написать запрос поиска AR, чтобы упорядочить результаты по количеству записей в ассоциации has_many?
class User < ActiveRecord::Base
has_many :photos
end
Я хочу сделать что-то вроде...
User.find(:all, :order => photos.count)
Я понимаю, что моя находка не является допустимым кодом. Скажем, у меня есть следующие данные.
User 1, which has 3 photos
User 2, which has 5 photos
User 3, which has 2 photos
Я хочу, чтобы моя находка вернула мне пользователей в порядке...
User 2,
User 1,
User 3
на основе количества фотографий пользователей
Ответы
Ответ 1
Если вам не нужен дополнительный столбец, вы всегда можете запросить дополнительный столбец в возвращаемом наборе результатов:
User.all(:select => "#{User.table_name}.*, COUNT(#{Photo.table_name}.id) number_of_photos",
:joins => :photos,
:order => "number_of_photos")
Это генерирует следующий SQL:
SELECT users.*, COUNT(photos.id) number_of_photos
FROM `users` INNER JOIN `photos` ON photos.user_id = users.id
ORDER BY number_of_photos
Ответ 2
Самый простой способ достичь этого - это, вероятно, добавить кэш-счетчик к этой модели, а затем отсортировать по этому столбцу.
class Photo < ActiveRecord::Base
belongs_to :user, :counter_cache => true
end
И не забудьте добавить столбец в таблицу users
с именем photos_count
.
Тогда вы сможете...
User.find(:all, :order => 'photos_count')
Ответ 3
Если вы не хотите добавлять столбец кеша счетчика, единственным вариантом является сортировка после поиска. Если вы :include
ассоциация в вашей находке, вы не будете выполнять никаких дополнительных операций с базой данных.
users = User.find(:all, :include => :photos).sort_by { |u| -u.photos.size }
Обратите внимание на отрицательный знак в блоке sort_by
для сортировки с высокого на низкий.
Ответ 4
Я бы посоветовал вам не писать прямой SQL, так как его реализация может отличаться от хранилища к хранилищу. К счастью, у вас есть isl:
User.joins(:photos).group(Photo.arel_table[:user_id]).
order(Photo.arel_table[:user_id].count)
Ответ 5
Counter cache поможет, но вам понадобится дополнительный столбец в db.
Ответ 6
Я бы добавил это как комментарий к верхнему ответу, но по какой-то причине я не могу. Согласно этому сообщению:
http://m.onkey.org/active-record-query-interface
Метод User.all(options)
будет устаревать после Rails 3.0.3 и заменен кучей других (удобных, цельных) активных типов записей, но очень сложно понять, как собрать один и тот же вид запроса.
В результате я пошел вперед и внедрил метод счетчика. Это было довольно легко и безболезненно, за исключением того, что вам нужно помнить об обновлении информации о столбцах в вашей миграции, иначе все существующие записи будут иметь "0".
Вот что я использовал в моей миграции:
class AddUserCountToCollections < ActiveRecord::Migration
def self.up
add_column :collections, :collectionusers_count, :integer, :default => 0
Collection.reset_column_information
Collection.all.each do |c|
Collection.update_counters c.id, :collectionusers_count => c.collectionusers.count
end
end
def self.down
remove_column :collections, :collectionusers_count
end
end
В теории это тоже должно быть быстрее. Надеюсь, что это поможет в будущем.
Ответ 7
Ваш вопрос не имеет смысла. Параметр :order
указывает имя столбца и необязательное направление направления, то есть asc (окончание) или desc (окончание).
Каков результат, которого вы пытаетесь достичь?