And*_*imm 26 sqlite activerecord ruby-on-rails find rails-activerecord
我想获得一个给定一组id的ActiveRecord对象数组.
我认为
Object.find([5,2,3])
Run Code Online (Sandbox Code Playgroud)
将返回一个数组,其中包含对象5,对象2,然后按顺序返回对象3,但我得到的数组按对象2,对象3和对象5排序.
ActiveRecord Base 查找方法API提到您不应该按照提供的顺序期望它(其他文档不提供此警告).
一个可能的解决方案是按相同顺序的ID数组查找的?,但订单选项似乎对SQLite无效.
我可以编写一些ruby代码来自己对对象进行排序(有点简单,缩放比例较差或缩放比较复杂),但是有更好的方法吗?
tom*_*fro 22
MySQL和其他数据库并不是自己排序的,而是他们不对它们进行排序.当您调用时Model.find([5, 2, 3]),生成的SQL类似于:
SELECT * FROM models WHERE models.id IN (5, 2, 3)
Run Code Online (Sandbox Code Playgroud)
这不指定订单,只指定要返回的记录集.事实证明,通常MySQL会按'id'顺序返回数据库行,但不能保证这一点.
使数据库以保证顺序返回记录的唯一方法是添加一个order子句.如果您的记录将始终按特定顺序返回,那么您可以向db添加排序列并执行Model.find([5, 2, 3], :order => 'sort_column').如果不是这种情况,您将不得不在代码中进行排序:
ids = [5, 2, 3]
records = Model.find(ids)
sorted_records = ids.collect {|id| records.detect {|x| x.id == id}}
Run Code Online (Sandbox Code Playgroud)
Sch*_*ems 10
基于我之前对Jeroen van Dijk的评论,您可以更有效地使用两行进行此操作 each_with_object
result_hash = Model.find(ids).each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
ids.map {|id| result_hash[id]}
Run Code Online (Sandbox Code Playgroud)
这里参考我使用的基准
ids = [5,3,1,4,11,13,10]
results = Model.find(ids)
Benchmark.measure do
100000.times do
result_hash = results.each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
ids.map {|id| result_hash[id]}
end
end.real
#=> 4.45757484436035 seconds
Run Code Online (Sandbox Code Playgroud)
现在是另一个
ids = [5,3,1,4,11,13,10]
results = Model.find(ids)
Benchmark.measure do
100000.times do
ids.collect {|id| results.detect {|result| result.id == id}}
end
end.real
# => 6.10875988006592
Run Code Online (Sandbox Code Playgroud)
更新
您可以在大多数情况下使用order和case语句执行此操作,这是您可以使用的类方法.
def self.order_by_ids(ids)
order_by = ["case"]
ids.each_with_index.map do |id, index|
order_by << "WHEN id='#{id}' THEN #{index}"
end
order_by << "end"
order(order_by.join(" "))
end
# User.where(:id => [3,2,1]).order_by_ids([3,2,1]).map(&:id)
# #=> [3,2,1]
Run Code Online (Sandbox Code Playgroud)
显然,mySQL和其他数据库管理系统可以自行排序.我认为你可以绕过这样做:
ids = [5,2,3]
@things = Object.find( ids, :order => "field(id,#{ids.join(',')})" )
Run Code Online (Sandbox Code Playgroud)
可移植的解决方案是在ORDER BY中使用SQL CASE语句.您可以在ORDER BY中使用几乎任何表达式,CASE可以用作内联查找表.例如,您之后的SQL将如下所示:
select ...
order by
case id
when 5 then 0
when 2 then 1
when 3 then 2
end
Run Code Online (Sandbox Code Playgroud)
用一点Ruby生成它很容易:
ids = [5, 2, 3]
order = 'case id ' + (0 .. ids.length).map { |i| "when #{ids[i]} then #{i}" }.join(' ') + ' end'
Run Code Online (Sandbox Code Playgroud)
以上假设您正在处理数字或其他一些安全值ids; 如果不是这种情况,那么你想要使用connection.quote或者使用一种ActiveRecord SQL清理程序方法来正确引用你的ids.
然后使用order字符串作为您的订购条件:
Object.find(ids, :order => order)
Run Code Online (Sandbox Code Playgroud)
或者在现代世界:
Object.where(:id => ids).order(order)
Run Code Online (Sandbox Code Playgroud)
这有点冗长,但它应该对任何SQL数据库都一样,并且隐藏丑陋并不困难.
| 归档时间: |
|
| 查看次数: |
20654 次 |
| 最近记录: |