Clean way to find ActiveRecord objects by id in the order specified Clean way to find ActiveRecord objects by id in the order specified sqlite sqlite

Clean way to find ActiveRecord objects by id in the order specified


It's not that MySQL and other DBs sort things on their own, it's that they don't sort them. When you call Model.find([5, 2, 3]), the SQL generated is something like:

SELECT * FROM models WHERE models.id IN (5, 2, 3)

This doesn't specify an order, just the set of records you want returned. It turns out that generally MySQL will return the database rows in 'id' order, but there's no guarantee of this.

The only way to get the database to return records in a guaranteed order is to add an order clause. If your records will always be returned in a particular order, then you can add a sort column to the db and do Model.find([5, 2, 3], :order => 'sort_column'). If this isn't the case, you'll have to do the sorting in code:

ids = [5, 2, 3]records = Model.find(ids)sorted_records = ids.collect {|id| records.detect {|x| x.id == id}} 


Based on my previous comment to Jeroen van Dijk you can do this more efficiently and in two lines using each_with_object

result_hash = Model.find(ids).each_with_object({}) {|result,result_hash| result_hash[result.id] = result }ids.map {|id| result_hash[id]}

For reference here is the benchmark i used

ids = [5,3,1,4,11,13,10]results = Model.find(ids)Benchmark.measure do   100000.times do     result_hash = results.each_with_object({}) {|result,result_hash| result_hash[result.id] = result }    ids.map {|id| result_hash[id]}  endend.real#=>  4.45757484436035 seconds

Now the other one

ids = [5,3,1,4,11,13,10]results = Model.find(ids)Benchmark.measure do   100000.times do     ids.collect {|id| results.detect {|result| result.id == id}}  endend.real# => 6.10875988006592

Update

You can do this in most using order and case statements, here is a class method you could use.

def self.order_by_ids(ids)  order_by = ["case"]  ids.each_with_index.map do |id, index|    order_by << "WHEN id='#{id}' THEN #{index}"  end  order_by << "end"  order(order_by.join(" "))end#   User.where(:id => [3,2,1]).order_by_ids([3,2,1]).map(&:id) #   #=> [3,2,1]


Apparently mySQL and other DB management system sort things on their own. I think that you can bypass that doing :

ids = [5,2,3]@things = Object.find( ids, :order => "field(id,#{ids.join(',')})" )