ActiveRecord.find(array_of_ids),保存顺序

在Rails中执行Something.find(array_of_ids)时,结果数组的顺序不依赖于array_of_ids的顺序。

有什么办法可以find并保存订单?

自动柜员机我手动sortinglogging基于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] 

奇怪的是,没有人有这样的build议:

 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的SQL查询来检索一个查询中的所有logging。 这比单独检索每个ID更快,但是如您注意到的,它不会保留您正在检索的logging的顺序。

为了解决这个问题,您可以根据查找logging时使用的原始ID列表,在应用程序级别对logging进行sorting。

根据很多优秀的答案来根据另一个数组的元素对数组进行sorting ,我推荐以下解决scheme:

 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 ?)

正如我在这里回答的,我刚刚发布了一个gem( order_as_specified ),它允许你像这样做原生的SQL命令:

 Something.find(array_of_ids).order_as_specified(id: array_of_ids) 

据我所能testing,它在所有的RDBMSes本地工作,并返回一个可以链接的ActiveRecord关系。

在SQL中不可能在所有情况下都能正常工作,你可能需要为每个logging或者ruby命令编写单个发现,尽pipe可能有办法使用专有技术来工作:

第一个例子:

 sorted = arr.inject([]){|res, val| res << Model.find(val)} 

非常不充分

第二个例子:

 unsorted = Model.find(arr) sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}} 

@Gunchars的答案很好,但是它不能在Rails 2.3中使用,因为Hash类没有sorting。 一个简单的解决方法是扩展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) 

在引擎盖下, find一个ID数组将会产生一个WHERE id IN...子句的SELECT ,这应该比循环通过ID更有效。

所以在一次访问数据库时,请求就满足了,但没有ORDER BY子句的SELECT是未sorting的。 ActiveRecord理解这一点,所以我们扩展我们的find如下:

 Something.find(array_of_ids, :order => 'id') 

如果你的数组中的ids的顺序是任意的和有意义的(即你想要返回的行的顺序与你的数组相匹配,而不考虑其中包含的id的顺序),那么我认为你是最好的服务器后处理结果代码 – 你可以创build一个:order子句,但是这将是非常复杂的,完全没有意图的揭示。

find(:order =>'…')中有一个订单子句,它在获取logging时执行此操作。 你也可以从这里得到帮助。

链接文本