当前位置:  开发笔记 > 数据库 > 正文

清洁方式按指定的顺序按ID查找ActiveRecord对象

如何解决《清洁方式按指定的顺序按ID查找ActiveRecord对象》经验,为你挑选了4个好方法。

我想获得一个给定一组id的ActiveRecord对象数组.

我认为

Object.find([5,2,3])

将返回一个数组,其中包含对象5,对象2,然后按顺序返回对象3,但我得到的数组按对象2,对象3和对象5排序.

ActiveRecord Base 查找方法API提到您不应该按照提供的顺序期望它(其他文档不提供此警告).

一个可能的解决方案是按相同顺序的ID数组查找的?,但订单选项似乎对SQLite无效.

我可以编写一些ruby代码来自己对对象进行排序(有点简单,缩放比例较差或缩放比较复杂),但是有更好的方法吗?



1> tomafro..:

MySQL和其他数据库并不是自己排序的,而是他们不对它们进行排序.当您调用时Model.find([5, 2, 3]),生成的SQL类似于:

SELECT * FROM models WHERE models.id IN (5, 2, 3)

这不指定订单,只指定要返回的记录集.事实证明,通常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}} 


另一种解决方案比使用#detect以避免O(N)性能更高效:records = Model.find(ids).group_by(&:id); sorted_records = ids.map {| id | 记录[id] .first}

2> Schneems..:

基于我之前对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]}

这里参考我使用的基准

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

现在是另一个

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

更新

您可以在大多数情况下使用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]



3> marcgg..:

显然,mySQL和其他数据库管理系统可以自行排序.我认为你可以绕过这样做:

ids = [5,2,3]
@things = Object.find( ids, :order => "field(id,#{ids.join(',')})" )


它似乎不适用于Postgres 9
这不再有效.对于更新的Rails:`Object.where(id:ids).order("field(id,#{ids.join','})")`

4> mu is too sh..:

可移植的解决方案是在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

用一点Ruby生成它很容易:

ids = [5, 2, 3]
order = 'case id ' + (0 .. ids.length).map { |i| "when #{ids[i]} then #{i}" }.join(' ') + ' end'

以上假设您正在处理数字或其他一些安全值ids; 如果不是这种情况,那么你想要使用connection.quote或者使用一种ActiveRecord SQL清理程序方法来正确引用你的ids.

然后使用order字符串作为您的订购条件:

Object.find(ids, :order => order)

或者在现代世界:

Object.where(:id => ids).order(order)

这有点冗长,但它应该对任何SQL数据库都一样,并且隐藏丑陋并不困难.


我用`Object.order('case id'+ ids.each_with_index.map {| id,i |"当#{id}然后#{i}"} .join('')+'end')来做.
推荐阅读
ifx0448363
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有