按ID给出的模型记录按ID数组的顺序查找

时间:2012-04-14 01:07:11

标签: ruby-on-rails ruby-on-rails-3 activerecord

我有一个查询来获取特定订单中人员的ID,请说: ids = [1, 3, 5, 9, 6, 2]

然后,我想通过Person.find(ids)

获取这些人

但它们总是以数字顺序提取,我通过执行以下方式知道:

people = Person.find(ids).map(&:id)
 => [1, 2, 3, 5, 6, 9]

如何运行此查询以使顺序与ids数组的顺序相同?

我使这个任务变得更加困难,因为我只想执行查询以从给定的ID中获取一次。因此,执行多个查询是不可能的。

我尝试过类似的事情:

ids.each do |i|
  person = people.where('id = ?', i)

但我认为这不起作用。

12 个答案:

答案 0 :(得分:30)

关于此代码的注意事项:

ids.each do |i|
  person = people.where('id = ?', i)

它有两个问题:

首先,#each方法返回它迭代的数组,所以你只需要返回id。你想要的是一个收集

其次,where将返回一个Arel :: Relation对象,它最终会被评估为一个数组。所以你最终得到了一个数组数组。你可以修复两种方式。

第一种方式是扁平化:

ids.collect {|i| Person.where('id => ?', i) }.flatten

更好的版本:

ids.collect {|i| Person.where(:id => i) }.flatten

第二种方法是简单地进行查找:

ids.collect {|i| Person.find(i) }

那很好很简单

然而,您会发现这些都会对每次迭代进行查询,因此效率不高。

我喜欢塞尔吉奥的解决方案,但这是我建议的另一个:

people_by_id = Person.find(ids).index_by(&:id) # Gives you a hash indexed by ID
ids.collect {|id| people_by_id[id] }

我发誓,我记得ActiveRecord曾经为我们做过这个ID订购。也许它与Arel一起消失了;)

答案 1 :(得分:11)

有两种方法可以通过给定一组id来获取条目。如果您正在使用Rails 4,则不推荐使用动态方法,您需要查看下面的Rails 4特定解决方案。

解决方案一:

Person.find([1,2,3,4])

如果没有记录,这将引发ActiveRecord::RecordNotFound

解决方案2 [仅限Rails 3]:

Person.find_all_by_id([1,2,3,4])

这不会导致异常,如果没有记录与您的查询匹配,则返回空数组。

根据您的要求选择您要在上面使用的方法,然后按给定ids

对其进行排序
ids = [1,2,3,4]
people = Person.find_all_by_id(ids)
# alternatively: people = Person.find(ids)
ordered_people = ids.collect {|id| people.detect {|x| x.id == id}}

解决方案[仅限Rails 4]:

我认为Rails 4提供了更好的解决方案。

# without eager loading
Person.where(id: [1,2,3,4]).order('id DESC')

# with eager loading.
# Note that you can not call deprecated `all`
Person.where(id: [1,2,3,4]).order('id DESC').load

答案 2 :(得分:10)

正如我所看到的,您可以映射ID或对结果进行排序。对于后者,已经有解决方案,但我发现它们效率低下。

映射ID:

ids = [1, 3, 5, 9, 6, 2]
people_in_order = ids.map { |id| Person.find(id) }

请注意,这将导致执行多个查询,这可能效率低下。

对结果进行排序:

ids = [1, 3, 5, 9, 6, 2]
id_indices = Hash[ids.map.with_index { |id,idx| [id,idx] }] # requires ruby 1.8.7+
people_in_order = Person.find(ids).sort_by { |person| id_indices[person.id] }

或者,扩展Brian Underwoods回答:

ids = [1, 3, 5, 9, 6, 2]
indexed_people = Person.find(ids).index_by(&:id) # I didn't know this method, TIL :)
people_in_order = indexed_people.values_at(*ids)

希望有所帮助

答案 3 :(得分:7)

如果您有ids数组,则它就像 - Person.where(id: ids).sort_by {|p| ids.index(p.id) } 或者

persons = Hash[ Person.where(id: ids).map {|p| [p.id, p] }] ids.map {|i| persons[i] }

答案 4 :(得分:5)

旧问题,但可以通过使用SQL FIELD函数进行排序来完成排序。 (仅用MySQL测试过。)

所以在这种情况下,这样的事情应该有效:

Person.order(Person.send(:sanitize_sql_array, ['FIELD(id, ?)', ids])).find(ids)

这导致以下SQL:

SELECT * FROM people
  WHERE id IN (1, 3, 5, 9, 6, 2)
  ORDER BY FIELD(id, 1, 3, 5, 9, 6, 2)

答案 5 :(得分:4)

您可以从数据库中获取按id asc排序的用户,然后以任意方式在应用程序中重新排列它们。看看这个:

ids = [1, 3, 5, 9, 6, 2]
users = ids.sort.map {|i| {id: i}} # Or User.find(ids) or another query

# users sorted by id asc (from the query)
users # => [{:id=>1}, {:id=>2}, {:id=>3}, {:id=>5}, {:id=>6}, {:id=>9}]

users.sort_by! {|u| ids.index u[:id]} 

# users sorted as you wanted
users # => [{:id=>1}, {:id=>3}, {:id=>5}, {:id=>9}, {:id=>6}, {:id=>2}]

这里的技巧是通过一个人为值对数组进行排序:对象在另一个数组中的id的索引。

答案 6 :(得分:2)

大多数其他解决方案都不允许您进一步过滤生成的查询,这就是我喜欢Koen's answer的原因。

与答案类似,但对于Postgres,我将此函数添加到我的ApplicationRecord(Rails 5+)或任何模型(Rails 4):

def self.order_by_id_list(id_list)
  values_clause = id_list.each_with_index.map{|id, i| "(#{id}, #{i})"}.join(", ")
  joins("LEFT JOIN (VALUES #{ values_clause }) AS #{ self.table_name}_id_order(id, ordering) ON #{ self.table_name }.id = #{ self.table_name }_id_order.id")
    .order("#{ self.table_name }_id_order.ordering")
end

查询解决方案来自this question

答案 7 :(得分:1)

如果您使用的是MySQL,这可能是一个简单的解决方案。

Post.where(sky: 'blue').order("FIELD(sort_item_field_id, 2,5,1,7,3,4)")

答案 8 :(得分:0)

在Rails 5中,我发现这种方法(至少适用于postgres)有效,甚至对于作用域查询也是如此,这对使用ElasticSearch很有用:

Person.where(country: "France").find([3, 2, 1]).map(&:id)
=> [3, 2, 1]

请注意,使用where代替find不会保留顺序。

Person.where(country: "France").where([3, 2, 1]).map(&:id)
=> [1, 2, 3]

答案 9 :(得分:0)

这种简单的解决方案的成本要低于加入值的成本:

order_query = <<-SQL
  CASE persons.id 
    #{ids.map.with_index { |id, index| "WHEN #{id} THEN #{index}" } .join(' ')}
    ELSE #{ids.length}
  END
SQL
Person.where(id: ids).order(order_query)

答案 10 :(得分:0)

要获取特定顺序的人员ID,请说:ids = [1, 3, 5, 9, 6, 2]

在旧版本的rails中,找到并在其中按数字顺序获取数据,但是rails 5以与查询顺序相同的顺序获取数据

注意:查找保留订单以及不保留订单的地方

Person.find(ids).map(&:id)
=> [1, 3, 5, 9, 6, 2]

Person.where(id: ids).map(&:id)
=> [1, 2, 3, 5, 6, 9] 

但是它们总是按数字顺序获取的,我通过执行以下操作知道这一点:

答案 11 :(得分:0)

我尝试了在 Rails6 上推荐 FIELD 方法的答案,但遇到了错误。但是,我发现只需将 sql 包装在 Arel.sql() 中即可。

# Make sure it's a known-safe values.
user_ids = [3, 2, 1]
# Before 
users = User.where(id: user_ids).order("FIELD(id, 2, 3, 1)")
# With warning.

# After 
users = User.where(id: user_ids).order(Arel.sql("FIELD(id, 2, 3, 1)"))
# No warning

[1] https://medium.com/@mitsun.chieh/activerecord-relation-with-raw-sql-argument-returns-a-warning-exception-raising-8999f1b9898a