RailsでSomething.find(array_of_ids)
を実行すると、結果の配列の順序はarray_of_ids
の順序に依存しません。
注文を見つけて保存する方法はありますか?
ATM私はIDの順序に基づいてレコードを手動でソートしますが、それは一種のラメです。
UPD::order
paramと何らかのSQL句を使用して順序を指定できる場合、どのようになりますか?
答えはmysqlのみです
Mysqlには FIELD() という関数があります
.find()で使用する方法は次のとおりです。
>> ids = [100, 1, 6]
=> [100, 1, 6]
>> WordDocument.find(ids).collect(&:id)
=> [1, 6, 100]
>> WordDocument.find(ids, :order => "field(id, #{ids.join(',')})")
=> [100, 1, 6]
For new Version
>> WordDocument.where(id: ids).order("field(id, #{ids.join ','})")
奇妙なことに、誰もこのようなことを提案していません。
index = Something.find(array_of_ids).group_by(&:id)
array_of_ids.map { |i| index[i].first }
SQLバックエンドに実行させる以外に効率的です。
編集:私自身の答えを改善するために、次のようにすることもできます:
Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
#index_by
および#slice
は、それぞれ配列とハッシュの ActiveSupport に非常に便利な追加です。
Mike Woodhouse で 彼の答え で述べられているように、これは、フードの下でRailsがWHERE id IN... clause
1つのクエリですべてのレコードを取得します。これは、各IDを個別に取得するよりも高速ですが、お気づきのとおり、取得するレコードの順序は保持されません。
これを修正するには、レコードを検索するときに使用したIDの元のリストに従って、アプリケーションレベルでレコードを並べ替えます。
別の配列の要素に従って配列を並べ替える に対する多くの優れた答えに基づいて、次の解決策をお勧めします:
Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}
または、もう少し高速なものが必要な場合(ただし、おそらく多少読みにくい)、これを行うことができます:
Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)
これはpostgresql( source )で動作するようで、ActiveRecordの関係を返します
_class Something < ActiveRecrd::Base
scope :for_ids_with_order, ->(ids) {
order = sanitize_sql_array(
["position((',' || id::text || ',') in ?)", ids.join(',') + ',']
)
where(:id => ids).order(order)
}
end
# usage:
Something.for_ids_with_order([1, 3, 2])
_
他の列にも拡張できます。 name
列には、position(name::text in ?)
を使用します...
here と答えたように、gem( order_as_specified )をリリースしました。これにより、次のようなネイティブSQLの順序付けが可能になります。
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
私がテストできた限り、それはすべてのRDBMSでネイティブに動作し、連鎖可能なActiveRecord関係を返します。
残念ながらすべてのケースで機能するSQLでは不可能ですが、Rubyでレコードごとに単一の検索結果を作成するか、注文する必要がありますが、おそらく独自の手法を使用して機能させる方法があります。
最初の例:
sorted = arr.inject([]){|res, val| res << Model.find(val)}
非常に不十分
2番目の例:
unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}
@Guncharsの答えは素晴らしいですが、Rails 2.3では箱から出して機能しません。Hashクラスが順序付けられていないためです。簡単な回避策は、Enumerableクラスを拡張することですindex_by
は、OrderedHashクラスを使用します。
module Enumerable
def index_by_with_ordered_hash
inject(ActiveSupport::OrderedHash.new) do |accum, elem|
accum[yield(elem)] = elem
accum
end
end
alias_method_chain :index_by, :ordered_hash
end
@Guncharsのアプローチが機能するようになりました
Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
ボーナス
module ActiveRecord
class Base
def self.find_with_relevance(array_of_ids)
array_of_ids = Array(array_of_ids) unless array_of_ids.is_a?(Array)
self.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
end
end
end
それから
Something.find_with_relevance(array_of_ids)
Model.pluck(:id)
が[1,2,3,4]
を返し、[2,4,1,3]
の順序が必要だと仮定します
概念は、ORDER BY CASE WHEN
SQL句を利用することです。例えば:
SELECT * FROM colors
ORDER BY
CASE
WHEN code='blue' THEN 1
WHEN code='yellow' THEN 2
WHEN code='green' THEN 3
WHEN code='red' THEN 4
ELSE 5
END, name;
Railsでは、同様の構造を構築するためにモデルにパブリックメソッドを含めることでこれを実現できます。
def self.order_by_ids(ids)
if ids.present?
order_by = ["CASE"]
ids.each_with_index do |id, index|
order_by << "WHEN id='#{id}' THEN #{index}"
end
order_by << "END"
order(order_by.join(" "))
end
else
all # If no ids, just return all
end
それから:
ordered_by_ids = [2,4,1,3]
results = Model.where(id: ordered_by_ids).order_by_ids(ordered_by_ids)
results.class # Model::ActiveRecord_Relation < ActiveRecord::Relation
これについての良いところ。結果はActiveRecord Relationsとして返されます(last
、count
、where
、pluck
などのメソッドを使用できます)
内部では、IDの配列を持つfind
は、WHERE id IN...
句を持つSELECT
を生成します。これは、IDをループするよりも効率的です。
したがって、データベースへの1回の旅行で要求は満たされますが、ORDER BY
句のないSELECT
sはソートされません。 ActiveRecordはこれを理解しているため、次のようにfind
を展開します。
Something.find(array_of_ids, :order => 'id')
配列内のIDの順序がarbitrary意的で重要な場合(つまり、含まれるIDの順序に関係なく、返される行の順序を配列に一致させたい場合)、コード-:order
句を作成できますが、それは非常に複雑であり、意図を明らかにするものではありません。
Gem find_with_order があり、ネイティブSQLクエリを使用して効率的に実行できます。
また、Mysql
とPostgreSQL
の両方をサポートしています。
例えば:
Something.find_with_order(array_of_ids)
関係が必要な場合:
Something.where_with_order(:id, array_of_ids)
CHANGELOGのどこにも言及されていませんが、バージョン5.2.0
のリリースでは、この機能 変更された のように見えます。
ここで commit5.2.0
でタグ付けされたドキュメントを更新していますが、 backported がバージョン5.0
になっているようです。
答えを参照して here
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
はPostgresqlで機能します。