在Rails中执行Something.find(array_of_ids)
时,结果数组的顺序不依赖于array_of_ids
的顺序。
有没有办法查找并保存订单?
ATM我根据ID的顺序手动对记录进行排序,但这有点蹩脚。
UPD:如果可以使用:order
param和某种SQL子句指定顺序,那么如何?
答案 0 :(得分:76)
奇怪的是,没有人提出这样的建议:
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中是非常方便的补充。
答案 1 :(得分:68)
答案仅适用于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]
答案 2 :(得分:36)
Mike Woodhouse中提到的his answer,发生这种情况,因为Rails正在使用带有WHERE id IN... clause
的SQL查询来检索一个查询中的所有记录。这比单独检索每个id要快,但正如您所注意到的那样,它不会保留您要检索的记录的顺序。
为了解决这个问题,您可以根据查找记录时使用的原始ID列表在应用程序级别对记录进行排序。
根据Sort an array according to the elements of another array的许多优秀答案,我建议采用以下解决方案:
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)
答案 3 :(得分:18)
这似乎适用于 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 ?)
...
答案 4 :(得分:17)
当我回答here时,我刚刚发布了一个gem(order_as_specified),它允许你像这样进行本机SQL排序:
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
就我已经能够测试而言,它在所有RDBMS中本地工作,并返回可以链接的ActiveRecord关系。
答案 5 :(得分:5)
不幸的是,在SQL中不可能在所有情况下工作,你需要为ruby中的每个记录或订单编写单个查找,尽管有可能使用专有技术使其工作:
第一个例子:
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}}
答案 6 :(得分:2)
@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)
答案 7 :(得分:1)
在幕后,带有一系列ID的find
将生成一个带有SELECT
子句的WHERE id IN...
,这应该比循环使用ID更有效。
因此,在数据库的一次访问中满足请求,但没有SELECT
子句的ORDER BY
未被排除。 ActiveRecord理解这一点,因此我们扩展find
如下:
Something.find(array_of_ids, :order => 'id')
如果你的数组中的id的顺序是任意的和重要的(即你想要返回的行的顺序与你的数组匹配,而不管其中包含的id序列),那么我认为你通过后处理成为最好的服务器代码中的结果 - 你可以建立一个:order
条款,但这将是非常复杂的,并没有任何意图揭示。
答案 8 :(得分:1)
虽然我在CHANGELOG的任何地方都没有看到它,但看起来这个功能was changed的版本为5.2.0
。
此处commit更新使用5.2.0
标记的文档但是它似乎也backported版本为5.0
。
答案 9 :(得分:1)
假设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
等方法)
答案 10 :(得分:1)
有一个find_with_order宝石,可让您通过使用本机SQL查询来高效地完成此任务。
它同时支持Mysql
和PostgreSQL
。
例如:
Something.find_with_order(array_of_ids)
如果您想要联系:
Something.where_with_order(:id, array_of_ids)
答案 11 :(得分:0)
参考答案here
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
适用于Postgresql。
答案 12 :(得分:0)
根据官方文档,其顺序应相同。
https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find
Something.find(array_of_ids)
结果数组的顺序应与array_of_ids相同。我已经在Rails 6中对此进行了测试。
答案 13 :(得分:-3)
find(:order =&gt;'...')中有一个order子句,它在获取记录时执行此操作。 你也可以从这里获得帮助。