ActiveRecord.find(array_of_ids),保留顺序

Leo*_*sov 93 ruby activerecord ruby-on-rails

当你Something.find(array_of_ids)在Rails中,所得到的数组的顺序不依赖的顺序array_of_ids.

有没有办法找到并保存订单?

ATM我根据ID的顺序手动对记录进行排序,但这有点蹩脚.

UPD:如果可以使用:orderparam和某种SQL子句指定顺序,那么如何?

Gun*_*ars 78

奇怪的是,没有人提出这样的建议:

index = Something.find(array_of_ids).group_by(&:id)
array_of_ids.map { |i| index[i].first }
Run Code Online (Sandbox Code Playgroud)

除了让SQL后端执行它之外,它的效率也很高.

编辑:为了改进我自己的答案,你也可以这样做:

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
Run Code Online (Sandbox Code Playgroud)

#index_by并且#sliceActiveSupport中分别用于数组和散列的非常方便的补充.

  • 这个问题是它返回一个数组,而不是一个关系. (12认同)
  • @Jon,订单在Ruby 1.9和其他试图遵循它的实现中得到保证.对于1.8,Rails(ActiveSupport)修补Hash类使其行为方式相同,所以如果你使用Rails,你应该没问题. (2认同)
  • 很棒,但是单行程对我不起作用(Rails 4.1) (2认同)

小智 69

答案仅适用于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 ','})")
Run Code Online (Sandbox Code Playgroud)

  • 这不再有效.对于更新的Rails:`Object.where(id:ids).order("field(id,#{ids.join','})")` (24认同)
  • 我在postgres中写了一个plpgsql函数来做到这一点 - http://omarqureshi.net/articles/2010-6-10-find-in-set-for-postgresql (3认同)
  • 这是一个比Gunchars更好的解决方案,因为它不会打破分页. (2认同)

Aje*_*i32 38

正如迈克伍德豪斯他的回答中所说的,这种情况发生时,Rails正在使用带有a的SQL查询WHERE id IN... clause来检索一个查询中的所有记录.这比单独检索每个id要快,但正如您所注意到的那样,它不会保留您要检索的记录的顺序.

为了解决这个问题,您可以根据查找记录时使用的原始ID列表在应用程序级别对记录进行排序.

基于根据另一个数组的元素对数组进行排序的许多优秀答案,我推荐以下解决方案:

Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}
Run Code Online (Sandbox Code Playgroud)

或者如果你需要更快一些东西(但可以说可读性稍差),你可以这样做:

Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)
Run Code Online (Sandbox Code Playgroud)

  • 第二个解决方案(使用index_by)似乎对我失败,产生所有零结果. (3认同)

gin*_*ime 20

这似乎适用于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])
Run Code Online (Sandbox Code Playgroud)

也可以扩展到其他列,例如对于name列,使用position(name::text in ?)...

  • 请注意,这仅适用于琐碎的情况,您最终将遇到您的Id包含在列表中的其他ID中的情况(例如,它将在11中找到1).解决这个问题的一种方法是将逗号添加到位置检查中,然后将最后一个逗号添加到连接中,如下所示:order = sanitize_sql_array(["position(','|| clients.id :: text ||', 'in?)",ids.join(',')+',']) (3认同)
  • 由于弃用错误,我必须将“order(order)”更改为“order(Arel.sql(order))”才能使其正常工作。 (2认同)

Jac*_*lyn 18

正如我在这里回答的那样,我刚刚发布了一个gem(order_as_specified),允许你像这样进行本机SQL排序:

Something.find(array_of_ids).order_as_specified(id: array_of_ids)
Run Code Online (Sandbox Code Playgroud)

就我能够测试而言,它在所有RDBMS中本地工作,并返回可以链接的ActiveRecord关系.

  • 伙计,你真是太棒了。谢谢你! (2认同)

kon*_*yak 6

根据官方文档,应该是相同的顺序。

https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find

Something.find(array_of_ids)

结果数组的顺序应与 array_of_ids 的顺序相同。我已经在 Rails 6 中对此进行了测试。


Oma*_*shi 5

不可能在SQL中不可能在所有情况下工作,你可能需要为ruby中的每个记录或订单编写单个查找,尽管有可能使用专有技术使其工作:

第一个例子:

sorted = arr.inject([]){|res, val| res << Model.find(val)}
Run Code Online (Sandbox Code Playgroud)

非常缺乏

第二个例子:

unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}
Run Code Online (Sandbox Code Playgroud)


khi*_*eoy 5

有一个 gem find_with_order可以让您通过使用本机 SQL 查询来高效地完成此操作。

它同时支持MysqlPostgreSQL

例如:

Something.find_with_order(array_of_ids)
Run Code Online (Sandbox Code Playgroud)

如果你想要关系:

Something.where_with_order(:id, array_of_ids)
Run Code Online (Sandbox Code Playgroud)