Answers:
答案仅适用于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
并且#slice
分别在ActiveSupport中非常方便地添加了数组和哈希。
正如Mike Woodhouse在回答中所说的那样,这可能是由于Rails在后台使用了带a的SQL查询WHERE id IN... clause
来检索一个查询中的所有记录。这比分别检索每个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 ?)
...
["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) }
有用的完整版本:谢谢@gingerlime @IrishDubGuy
正如我在这里回答的那样,我刚刚发布了一个gem(order_as_specified),它使您可以执行本机SQL排序,如下所示:
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
据我所能进行的测试,它在所有RDBMS中都可以正常运行,并且它返回可以链接的ActiveRecord关系。
不幸的是,在不可能在所有情况下都能正常工作的SQL中,您可能需要为红宝石的每条记录或订单编写单个查找,尽管可能有一种使用专有技术使其工作的方法:
第一个例子:
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}}
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
@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的关系返回(允许你使用类似的方法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...
子句的,它比遍历ID的效率更高。
因此,一次访问数据库就满足了该请求,但是SELECT
不带ORDER BY
子句的s 未排序。ActiveRecord理解这一点,因此我们将其扩展find
如下:
Something.find(array_of_ids, :order => 'id')
如果数组中id的顺序是任意且重要的(即,无论数组中包含的id顺序如何,您都希望返回的行顺序与数组匹配),那么我认为通过对结果进行后处理,您将是最好的服务器代码-您可以构建一个:order
子句,但它会非常复杂,而且根本不涉及意图。
:order => id
)
在find(:order =>'...')中有一个order子句,该子句在获取记录时执行此操作。您也可以从这里获得帮助。