Something.find(array_of_ids)
Railsで行う場合、結果の配列の順序はの順序に依存しませんarray_of_ids
。
注文を見つけて保存する方法はありますか?
ATM私はIDの順序に基づいて手動でレコードをソートしますが、それはちょっと不自然です。
UPD::order
paramとなんらかのSQL句を使用して順序を指定できる場合は、どうすればよいですか?
Something.find(array_of_ids)
Railsで行う場合、結果の配列の順序はの順序に依存しません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 ','})")
更新:これはRails 6.1 Railsソースコードで削除されます
FIELDS()
Postgres の同等の機能を知っていますか?
Object.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
および配列とハッシュのActiveSupportでそれぞれ#slice
かなり便利な追加です。
マイク・ウッドハウスの中で述べた彼の答えは、これはbecase発生し、ボンネットの下に、RailsはとSQLクエリを使用している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(ソース)で機能するようです-そして、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 ?)
...
["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ',']
ます:私のために機能したフルバージョン:scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) }
Thanks @gingerlime @IrishDubGuy
私がここで答えたように、次のようなネイティブSQLの順序付けを可能にするgem(order_as_specified)をリリースしました。
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
私がテストできた限り、それはすべてのRDBMSでネイティブに動作し、連鎖可能なActiveRecordリレーションを返します。
残念ながらすべてのケースで機能するSQLでは不可能です。独自の手法を使用して機能させる方法がおそらくあるかもしれませんが、ルビで各レコードまたは注文ごとに単一の検索結果を書き込む必要があります。
最初の例:
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}}
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
@Guncharsの回答はすばらしいですが、Hashクラスが順序付けされていないため、Rails 2.3ではそのままでは機能しません。簡単な回避策は、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の関係として返されlast
、count
、where
、pluck
、など)
宝石find_with_orderがありますネイティブSQLクエリを使用して効率的に実行できるます。
との両方Mysql
をサポートしていPostgreSQL
ます。
例えば:
Something.find_with_order(array_of_ids)
関係が必要な場合:
Something.where_with_order(:id, array_of_ids)
ボンネットの下で、find
IDの配列で生成されますSELECT
とをWHERE id IN...
IDSをループよりも効率的でなければならない句。
したがって、データベースへの1回の移動で要求は満たされますが、句SELECT
のないs ORDER BY
はソートされません。ActiveRecordはこれを理解するため、次のように拡張find
します。
Something.find(array_of_ids, :order => 'id')
配列内のIDの順序が任意で重要である場合(つまり、そこに含まれるIDのシーケンスに関係なく、返される行の順序が配列と一致するようにする場合)、コード- :order
句を作成することはできますが、それは非常に複雑で、意図を明らかにすることはできません。
:order => id
)
こちらの回答を参考に
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
Postgresqlで動作します。