Подтвердить что ты не робот

Найти записи модели по идентификатору в порядке, указанном массивом идентификаторов

У меня есть запрос на получение идентификаторов людей в определенном порядке, например: ids = [1, 3, 5, 9, 6, 2]

Затем я хочу получить этих людей Person.find(ids)

Но они всегда извлекаются в числовом порядке, я знаю это, выполняя:

people = Person.find(ids).map(&:id)
 => [1, 2, 3, 5, 6, 9]

Как я могу запустить этот запрос, чтобы порядок был таким же, как и порядок массива ids?

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

Я пробовал что-то вроде:

ids.each do |i|
  person = people.where('id = ?', i)

Но я не думаю, что это работает.

4b9b3361

Ответ 1

Примечание по этому коду:

ids.each do |i|
  person = people.where('id = ?', i)

Есть две проблемы:

Во-первых, метод #each возвращает массив, в который он был включен, поэтому вы просто вернете идентификаторы. Вы хотите собрать коллекцию

Во-вторых, где будет возвращен объект Arel:: Relation, который в конце будет оцениваться как массив. Таким образом, вы получите массив массивов. Вы можете исправить два пути.

Первый способ - сглаживание:

ids.collect {|i| Person.where('id => ?', i) }.flatten

Еще лучшая версия:

ids.collect {|i| Person.where(:id => i) }.flatten

Второй способ - просто найти find:

ids.collect {|i| Person.find(i) }

Что приятно и просто

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

Мне нравится решение Серджио, но здесь я бы предложил:

people_by_id = Person.find(ids).index_by(&:id) # Gives you a hash indexed by ID
ids.collect {|id| people_by_id[id] }

Я клянусь, что я помню, что ActiveRecord использовал этот идентификационный код для нас. Может быть, он ушел с Арелом;)

Ответ 2

Существует два способа получить записи по массиву идентификаторов. Если вы работаете с Rails 4, динамический метод устарел, вам нужно посмотреть на конкретное решение Rails 4 ниже.

Решение:

Person.find([1,2,3,4])

Это приведет к повышению ActiveRecord::RecordNotFound, если нет записи

Решение два [только Rails 3]:

Person.find_all_by_id([1,2,3,4])

Это не вызовет исключения, просто вернет пустой массив, если никакая запись не соответствует вашему запросу.

В соответствии с вашим требованием, выбирая метод, который вы хотели бы использовать выше, затем отсортируйте их с помощью ids

ids = [1,2,3,4]
people = Person.find_all_by_id(ids)
# alternatively: people = Person.find(ids)
ordered_people = ids.collect {|id| people.detect {|x| x.id == id}}

Решение [Только для Rails 4]:

Я думаю, что Rails 4 предлагает лучшее решение.

# without eager loading
Person.where(id: [1,2,3,4]).order('id DESC')

# with eager loading.
# Note that you can not call deprecated `all`
Person.where(id: [1,2,3,4]).order('id DESC').load

Ответ 3

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

Отображение идентификаторов:

ids = [1, 3, 5, 9, 6, 2]
people_in_order = ids.map { |id| Person.find(id) }

Обратите внимание, что это приведет к выполнению нескольких запросов, что потенциально неэффективно.

Сортировка результата:

ids = [1, 3, 5, 9, 6, 2]
id_indices = Hash[ids.map.with_index { |id,idx| [id,idx] }] # requires ruby 1.8.7+
people_in_order = Person.find(ids).sort_by { |person| id_indices[person.id] }

Или, расширившись на Брайана Андервуда, ответьте:

ids = [1, 3, 5, 9, 6, 2]
indexed_people = Person.find(ids).index_by(&:id) # I didn't know this method, TIL :)
people_in_order = indexed_people.values_at(*ids)

Надеюсь, что поможет

Ответ 4

Если у вас есть массив ids, то это так же просто, как - Person.where(id: ids).sort_by {|p| ids.index(p.id) } ИЛИ

persons = Hash[ Person.where(id: ids).map {|p| [p.id, p] }] ids.map {|i| persons[i] }

Ответ 5

Вы можете получить пользователей, отсортированных по id asc из базы данных, а затем упорядочить их в приложении любым способом. Проверьте это:

ids = [1, 3, 5, 9, 6, 2]
users = ids.sort.map {|i| {id: i}} # Or User.find(ids) or another query

# users sorted by id asc (from the query)
users # => [{:id=>1}, {:id=>2}, {:id=>3}, {:id=>5}, {:id=>6}, {:id=>9}]

users.sort_by! {|u| ids.index u[:id]} 

# users sorted as you wanted
users # => [{:id=>1}, {:id=>3}, {:id=>5}, {:id=>9}, {:id=>6}, {:id=>2}]

Трюк здесь сортирует массив по искусственному значению: индекс идентификатора объекта в другом массиве.

Ответ 6

Старый вопрос, но сортировка может быть выполнена путем упорядочения с помощью функции SQL FIELD. (Пробовал только это с MySQL.)

Итак, в этом случае что-то вроде этого должно работать:

Person.order(Person.send(:sanitize_sql_array, ['FIELD(id, ?)', ids])).find(ids)

В результате получается следующий SQL:

SELECT * FROM people
  WHERE id IN (1, 3, 5, 9, 6, 2)
  ORDER BY FIELD(id, 1, 3, 5, 9, 6, 2)

Ответ 7

Большинство других решений не позволяют вам фильтровать полученный запрос, поэтому мне нравится ответ Koen.

Подобно этому ответу, но для Postgres, я добавляю эту функцию в свой ApplicationRecord (Rails 5+) или в любую модель (Rails 4):

def self.order_by_id_list(id_list)
  values_clause = id_list.each_with_index.map{|id, i| "(#{id}, #{i})"}.join(", ")
  joins("LEFT JOIN (VALUES #{ values_clause }) AS #{ self.table_name}_id_order(id, ordering) ON #{ self.table_name }.id = #{ self.table_name }_id_order.id")
    .order("#{ self.table_name }_id_order.ordering")
end

Решение запроса от этого вопроса.

Ответ 8

Если вы используете MySQL, это может быть простым решением для вас.

Post.where(sky: 'blue').order("FIELD(sort_item_field_id, 2,5,1,7,3,4)")