문제

ID 배열이 주어진 ActiveRecord 객체 배열을 얻고 싶습니다.

나는 그것을 가정했다

Object.find([5,2,3])

객체 5, 객체 2, 객체 3을 순서대로 반환하지만 대신 객체 2, 객체 3, 객체 5로 주문한 배열을 얻습니다.

ActiveRecord 기반 방법 API를 찾으십시오 제공된 순서대로 기대하지 않아야한다고 언급합니다 (다른 문서는이 경고를 제공하지 않습니다).

하나의 잠재적 인 해결책이 제공되었습니다 동일한 순서로 ID 배열로 찾으십니까?, 그러나 주문 옵션은 SQLITE에 유효하지 않은 것 같습니다.

나는 객체를 직접 분류하기 위해 루비 코드를 작성할 수 있지만 (다소 단순하고 제대로 스케일링하거나 더 나은 스케일링과 더 복잡한) 더 나은 방법이 있습니까?

도움이 되었습니까?

해결책

MySQL과 다른 DBS가 스스로 분류하는 것이 아니라 분류하지 않는 것입니다. 전화 할 때 Model.find([5, 2, 3]), 생성 된 SQL은 다음과 같습니다.

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

이것은 주문을 지정하지 않고 반환하려는 레코드 세트 만 지정합니다. 일반적으로 MySQL은 데이터베이스 행을 반환 할 것입니다. 'id' 주문, 그러나 이것에 대한 보장은 없습니다.

데이터베이스가 보장 된 주문으로 레코드를 반환하도록하는 유일한 방법은 주문 조항을 추가하는 것입니다. 레코드가 항상 특정 순서로 반환되는 경우 DB에 정렬 열을 추가하고 수행 할 수 있습니다. Model.find([5, 2, 3], :order => 'sort_column'). 그렇지 않은 경우 코드에서 정렬을 수행해야합니다.

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

다른 팁

Jeroen van Dijk에 대한 나의 이전 의견을 기반으로 더 효율적이고 두 줄로이 작업을 수행 할 수 있습니다. 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]}

참고로 여기에 내가 사용한 벤치 마크가 있습니다

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]}
  end
end.real
#=>  4.45757484436035 seconds

이제 다른 하나

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}}
  end
end.real
# => 6.10875988006592

업데이트

순서 및 사례 문을 사용하여 대부분의 경우 사용할 수 있습니다. 여기에 사용할 수있는 클래스 방법이 있습니다.

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]

분명히 MySQL 및 기타 DB 관리 시스템은 스스로 정렬됩니다. 나는 당신이 그 일을 우회 할 수 있다고 생각합니다.

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

휴대용 솔루션은 주문시 SQL Case 문을 사용하는 것입니다. 순서대로 거의 모든 표현식을 사용할 수 있으며 케이스는 인라인 조회 테이블로 사용할 수 있습니다. 예를 들어, 당신이 후에 SQL이 다음과 같이 보일 것입니다.

select ...
order by
    case id
    when 5 then 0
    when 2 then 1
    when 3 then 2
    end

그것은 약간의 루비로 생성하기가 매우 쉽습니다.

ids = [5, 2, 3]
order = 'case id ' + (0 .. ids.length).map { |i| "when #{ids[i]} then #{i}" }.join(' ') + ' end'

위의 내용은 숫자 또는 다른 안전한 가치로 작업하고 있다고 가정합니다. ids; 그렇지 않은 경우 사용하고 싶을 것입니다. connection.quote 또는 중 하나 Activerecord SQL 소독제 방법 당신을 올바르게 인용하려면 ids.

그런 다음 사용하십시오 order 주문 조건으로 문자열 :

Object.find(ids, :order => order)

또는 현대 세계에서 :

Object.where(:id => ids).order(order)

이것은 약간의 장점이지만 SQL 데이터베이스와 동일하게 작동해야하며 추악함을 숨기는 것은 어렵지 않습니다.

내가 대답했던대로 여기, 방금 보석을 발표했습니다 (Order_as_specified) 이와 같이 기본 SQL 주문을 수행 할 수 있습니다.

Object.where(id: [5, 2, 3]).order_as_specified(id: [5, 2, 3])

방금 테스트하고 sqlite에서 작동합니다.

저스틴 와이즈는 썼다 이 문제에 대한 블로그 기사 이틀 전에.

데이터베이스에서 선호하는 순서에 대해 데이터베이스에 알리고 데이터베이스에서 직접 정렬 된 모든 레코드를로드하는 것은 좋은 방법 인 것 같습니다. 그의 예 블로그 기사:

# in config/initializers/find_by_ordered_ids.rb
module FindByOrderedIdsActiveRecordExtension
  extend ActiveSupport::Concern
  module ClassMethods
    def find_ordered(ids)
      order_clause = "CASE id "
      ids.each_with_index do |id, index|
        order_clause << "WHEN #{id} THEN #{index} "
      end
      order_clause << "ELSE #{ids.length} END"
      where(id: ids).order(order_clause)
    end
  end
end

ActiveRecord::Base.include(FindByOrderedIdsActiveRecordExtension)

이를 통해 다음을 쓸 수 있습니다.

Object.find_ordered([2, 1, 3]) # => [2, 1, 3]

다음은 방법으로 수행자 (Detect!)와 같이 O (N) 배열 검색이 아닌 해시 룩업이 있습니다.

def find_ordered(model, ids)
  model.find(ids).map{|o| [o.id, o]}.to_h.values_at(*ids)
end

# We get:
ids = [3, 3, 2, 1, 3]
Model.find(ids).map(:id)          == [1, 2, 3]
find_ordered(Model, ids).map(:id) == ids

루비에서 할 수있는 또 다른 (아마도 더 효율적인) 방법 :

ids = [5, 2, 3]
records_by_id = Model.find(ids).inject({}) do |result, record| 
  result[record.id] = record
  result
end
sorted_records = ids.map {|id| records_by_id[id] }

내가 생각해 낼 수있는 가장 간단한 것은 다음과 같습니다.

ids = [200, 107, 247, 189]
results = ModelObject.find(ids).group_by(&:id)
sorted_results = ids.map {|id| results[id].first }
@things = [5,2,3].map{|id| Object.find(id)}

각 ID에 대한 데이터베이스로의 여행이 필요하기 때문에 찾을 객체가 너무 많지 않다고 가정 할 때 이것은 아마도 가장 쉬운 방법 일 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top