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时执行此操作。 你也可以从这里得到帮助。
链接文本