2009-11-05 123 views
83

在Rails中做Something.find(array_of_ids)时,生成的数组的顺序不取决于顺序array_of_idsActiveRecord.find(array_of_ids),保留顺序

有什么办法可以查找和保存订单吗?

自动柜员机手动排序基于ID的顺序的记录,但这是一种跛脚。

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

+4

可能的重复[Clean方式按指定顺序查找ActiveRecord对象](http://stackoverflow.com/questions/801824/clean-way-to-find -active-order-objects-by-id-in-order-specified) – 2013-03-22 06:56:01

回答

61

答案是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] 
+0

谢谢!现在开始进行基准测试。 – 2009-11-06 12:35:22

+0

你碰巧知道在Postgres中相当于'FIELDS()'吗? – 2012-06-26 06:27:24

+3

我写了一个plpgsql函数在postgres中执行此操作 - http://omarqureshi.net/articles/2010-6-10-find-in-set-for-postgresql – 2012-06-29 16:58:50

-3

find((order =>'...')中有一个订单子句,它在获取记录时执行此操作。 你也可以从这里获得帮助。

link text

+0

好的。问题已更新。 – 2009-11-05 14:01:07

1

引擎盖下,find与IDS的阵列将产生具有WHERE id IN...子句,这应该是比通过IDS循环更有效的一个SELECT

因此,请求满足一次到数据库,但SELECT s没有ORDER BY子句是未排序的。 ActiveRecord的明白这一点,所以我们扩大find如下:

Something.find(array_of_ids, :order => 'id') 

如果IDS的数组中的顺序是任意的,显著(也就是说你想行的顺序返回不论ID匹配的序列的排列包含在其中),那么我认为你会是最好的服务器,通过代码后处理结果 - 你可以建立一个:order条款,但这将是非常复杂,并不意图透露。

+0

请注意,选项散列已被弃用。 (第二个参数,在这个例子中':order => id') – ocodo 2012-07-25 02:26:11

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}} 
+1

是的,第二个例子就是我现在正在做的事情。 – 2009-11-06 12:34:18

+0

虽然效率不高,但我同意这种解决方法是不依赖于数据库的,并且如果行数很少,则可以接受。 – 2012-06-26 06:26:49

+1

'map'而不是'reduce'(inject)会更具可读性。 – 2013-02-27 06:30:17

72

奇怪的是,没有有人建议这样的事情:

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#slice是非常方便的添加在ActiveSupport数组和哈希分别。

+0

所以你的编辑似乎工作,但它让我紧张的哈哈键顺序不能保证是吗?所以当你调用slice并将hash返回到“重新排序”时,它实际上取决于hash键返回值的顺序,即键被添加。这感觉像取决于可能改变的实现细节。 – Jon 2013-09-03 17:45:44

+2

@Jon,在Ruby 1.9和其他所有试图遵循它的实现中都保证了顺序。对于1.8,Rails(ActiveSupport)修补了Hash类,使其具有相同的行为方式,因此如果您使用Rails,则应该没问题。 – Gunchars 2013-09-03 21:39:01

+0

感谢您的澄清,只是在文档中发现。 – Jon 2013-09-04 04:46:36

2

@Gunchars的答案很棒,但它在Rails 2.3中并不适用,因为Hash类没有排序。简单的解决方法是延长枚举类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) 
31

由于his answer中陈述的Mike Woodhouse,这是因为在引擎盖下,Rails使用SQL查询与WHERE id IN... clause来检索一个查询中的所有记录。这比单独检索每个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) 
+2

第二个解决方案(使用index_by)似乎失败了,产生了所有零结果。 – 2016-12-13 20:36:39

16

正如我回答here,我刚刚发布了一个宝石(order_as_specified),它允许你做本地SQL排序是这样的:

Something.find(array_of_ids).order_as_specified(id: array_of_ids) 

就我所能测试的那样,它在所有的RDBMS中都能正常工作,并且它返回一个可链接的ActiveRecord关系。

+1

伙计,你真棒。谢谢! – swrobel 2016-01-21 23:18:39

16

这似乎为的PostgreSQLsource)工作 - 并返回一个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 ?) ...

+0

你是我这周的英雄。谢谢! – ntdb 2016-02-29 23:42:37

+3

请注意,这只适用于微不足道的情况,您最终会遇到一种情况,即您的Id包含在列表中的其他ID中(例如,它会在11中找到1)。解决方法之一是将逗号添加到位置检查中,然后向连接添加最终逗号,如下所示: order = sanitize_sql_array( [“position(','|| clients.id :: text | |','in?)“,ids.join(',')+','] ) – IrishDubGuy 2016-05-18 14:29:51

+0

好点,@IrishDubGuy!我会根据您的建议更新我的答案。谢谢! – gingerlime 2016-05-19 19:59:49