47

特定の順序で人のIDを取得するクエリがあります。たとえば、次のようにします。 ids = [1, 3, 5, 9, 6, 2]

次に、それらの人々を取得したいPerson.find(ids)

しかし、それらは常に番号順にフェッチされます。実行することでこれを知っています:

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

順序が ids 配列の順序と同じになるように、このクエリを実行するにはどうすればよいですか?

与えられた ID から一度だけ人を取得するクエリを実行したかったので、このタスクをより困難にしました。したがって、複数のクエリを実行することは問題外です。

私は次のようなものを試しました:

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

しかし、これはうまくいかないと思います。

4

13 に答える 13

35

このコードに関する注意:

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

これには 2 つの問題があります。

まず、#each メソッドは反復処理の対象となる配列を返すため、ID を取得するだけです。あなたが欲しいのは収集です

次に、where は Arel::Relation オブジェクトを返します。これは最終的に配列として評価されます。したがって、配列の配列になってしまいます。2 つの方法で修正できます。

最初の方法は、平坦化することです。

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

さらに良いバージョン:

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

2 番目の方法は、単純に検索を実行することです。

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 がこの ID の順序付けを行っていたことを覚えていることを誓います。多分それはArelで消えました;)

于 2012-04-14T01:37:10.773 に答える
16

ご覧のとおり、ID をマップするか、結果を並べ替えることができます。後者については、解決策はすでにありますが、非効率だと思います。

ID のマッピング:

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] }

または、Brian Underwoods の回答を拡張します。

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)

それが役立つことを願っています

于 2012-04-14T03:13:46.987 に答える
13

配列がある場合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] }

于 2014-09-04T11:00:40.137 に答える
9

ID の配列を指定してエントリを取得するには、2 つの方法があります。Rails 4 で作業している場合、動的メソッドは非推奨です。以下の Rails 4 固有のソリューションを確認する必要があります。

解決策 1:

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

これはActiveRecord::RecordNotFound、レコードが存在しない場合に発生します

解決策 2 [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
于 2012-04-14T02:13:41.970 に答える
8

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}]

ここでのトリックは、人為的な値 (別の配列内のオブジェクトの ID のインデックス) で配列をソートすることです。

于 2012-04-14T01:23:35.957 に答える
5

他のソリューションのほとんどでは、結果のクエリをさらにフィルタリングすることはできません。これが、 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

クエリの解決策は、この質問からのものです。

于 2017-04-05T16:13:21.790 に答える