我有一个查询来获取特定订单中人员的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)
但我认为这不起作用。
答案 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