Programing

ActiveRecord.find (array_of_ids), 순서 유지

lottogame 2020. 8. 31. 08:20
반응형

ActiveRecord.find (array_of_ids), 순서 유지


Something.find(array_of_ids)Rails에서 할 때 결과 배열의 순서는의 순서에 의존하지 않습니다 array_of_ids.

주문을 찾고 보존 할 수있는 방법이 있습니까?

ATM ID 순서에 따라 수동으로 레코드를 정렬하지만 다소 절름발이입니다.

UPD : :orderparam과 어떤 종류의 SQL 절 을 사용하여 순서를 지정할 수 있다면 어떻게할까요?


대답은 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]

For new Version
>> WordDocument.where(id: ids).order("field(id, #{ids.join ','})")

이상하게도 아무도 다음과 같은 제안을하지 않았습니다.

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 각각 배열과 해시에 대한이.


마이크 우드 하우스가 에 명시된 그의 대답 이는 후드 아래, 레일이 가진 SQL 쿼리를 사용하고, becase 발생 WHERE id IN... clause한 쿼리에서 모든 레코드를 검색 할 수 있습니다. 이 방법은 각 ID를 개별적으로 검색하는 것보다 빠르지 만 검색중인 레코드의 순서를 유지하지 않습니다.

이 문제를 해결하기 위해 레코드를 조회 할 때 사용한 원래 ID 목록에 따라 애플리케이션 수준에서 레코드를 정렬 할 수 있습니다.

Sort an array of 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)

이것은 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 ?)...


여기 에서 대답했듯이 다음과 같이 네이티브 SQL 주문을 수행 할 수 있는 gem ( order_as_specified )을 출시했습니다 .

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

내가 테스트 할 수있는 한 모든 RDBMS에서 기본적으로 작동하며 연결할 수있는 ActiveRecord 관계를 반환합니다.


불행히도 모든 경우에 작동하는 SQL에서는 가능하지 않습니다. 독점 기술을 사용하여 작동하도록 만드는 방법이 있지만 각 레코드 또는 주문에 대해 단일 찾기를 작성해야합니다.

First example:

sorted = arr.inject([]){|res, val| res << Model.find(val)}

VERY INEFFICIENT

Second example:

unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}

@Gunchars answer is great, but it doesn't work out of the box in Rails 2.3 because the Hash class is not ordered. A simple workaround is to extend the Enumerable class' index_by to use the OrderedHash class:

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

Now @Gunchars' approach will work

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values

Bonus

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

Then

Something.find_with_relevance(array_of_ids)

Assuming Model.pluck(:id) returns [1,2,3,4] and you want the order of [2,4,1,3]

The concept is to to utilize the ORDER BY CASE WHEN SQL clause. For example:

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;

In Rails, you can achieve this by having a public method in your model to construct a similar structure:

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

Then do:

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

The good thing about this. Results are returned as ActiveRecord Relations (allowing you to use methods like last, count, where, pluck, etc)


Under the hood, find with an array of ids will generate a SELECT with a WHERE id IN... clause, which should be more efficient than looping through the ids.

So the request is satisfied in one trip to the database, but SELECTs without ORDER BY clauses are unsorted. ActiveRecord understands this, so we expand our find as follows:

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

If the order of ids in your array is arbitrary and significant (i.e. you want the order of rows returned to match your array irrespective of the sequence of ids contained therein) then I think you'd be best server by post-processing the results in code - you could build an :order clause but it would be fiendishly complicated and not at all intention-revealing.


Although I don't see it mentioned anywhere in a CHANGELOG, it looks like this functionality was changed with the release of version 5.2.0.

Here commit updating the docs tagged with 5.2.0 However it appears to have also been backported into version 5.0.


There is a gem find_with_order which allows you to do it efficiently by using native SQL query.

And it supports both Mysql and PostgreSQL.

For example:

Something.find_with_order(array_of_ids)

If you want relation:

Something.where_with_order(:id, array_of_ids)

With reference to the answer here

Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')") works for Postgresql.


There is an order clause in find (:order=>'...') which does this when fetching records. You can get help from here also.

link text

참고URL : https://stackoverflow.com/questions/1680627/activerecord-findarray-of-ids-preserving-order

반응형