ActiveRecord.find(array_of_ids),保留顺序

时间:2009-11-05 13:34:45

标签: ruby-on-rails ruby activerecord

在Rails中执行Something.find(array_of_ids)时,结果数组的顺序不依赖于array_of_ids的顺序。

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

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

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

14 个答案:

答案 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#sliceActiveSupport中是非常方便的补充。

答案 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关系返回(允许您使用诸如lastcountwherepluck等方法)

答案 10 :(得分:1)

有一个find_with_order宝石,可让您通过使用本机SQL查询来高效地完成此任务。

它同时支持MysqlPostgreSQL

例如:

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子句,它在获取记录时执行此操作。 你也可以从这里获得帮助。

link text