Something.find(array_of_ids)
Rails에서 수행 할 때 결과 배열의 순서는array_of_ids
.
주문을 찾고 보존 할 수있는 방법이 있습니까?
ATM ID 순서에 따라 수동으로 레코드를 정렬하지만 다소 절름발이입니다.
UPD : :order
param과 어떤 종류의 SQL 절 을 사용하여 순서를 지정할 수 있다면 어떻게할까요?
Something.find(array_of_ids)
Rails에서 수행 할 때 결과 배열의 순서는array_of_ids
.
주문을 찾고 보존 할 수있는 방법이 있습니까?
ATM ID 순서에 따라 수동으로 레코드를 정렬하지만 다소 절름발이입니다.
UPD : :order
param과 어떤 종류의 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 ','})")
업데이트 : 이것은 Rails 6.1 Rails 소스 코드 에서 제거됩니다.
FIELDS()
Postgres에 해당하는 것을 알고 있습니까?
Object.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 ?)
...
["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ',']
나를 위해 일한 정식 버전 : scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) }
감사합니다 @gingerlime @IrishDubGuy
여기 에서 대답했듯이 다음과 같이 네이티브 SQL 주문을 수행 할 수 있는 gem ( order_as_specified )을 출시했습니다 .
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
내가 테스트 할 수있는 한 모든 RDBMS에서 기본적으로 작동하며 연결할 수있는 ActiveRecord 관계를 반환합니다.
불행히도 모든 경우에 작동하는 SQL에서는 가능하지 않습니다. 독점 기술을 사용하여 작동하도록 만드는 방법이 있지만 각 레코드 또는 주문에 대해 단일 찾기를 작성해야합니다.
첫 번째 예 :
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}}
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
@Gunchars 대답은 훌륭하지만 Hash 클래스가 주문되지 않았기 때문에 Rails 2.3에서 상자 밖으로 작동하지 않습니다. 간단한 해결 방법은 index_by
OrderedHash 클래스를 사용하도록 Enumerable 클래스를 확장하는 것입니다 .
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)
Model.pluck(:id)
반품을 가정 [1,2,3,4]
하고 주문을 원합니다.[2,4,1,3]
개념은 ORDER BY CASE WHEN
SQL 절 을 활용하는 것 입니다. 예를 들면 :
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;
Rails에서는 모델에 공용 메서드를 사용하여 유사한 구조를 구성함으로써이를 달성 할 수 있습니다.
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
다음을 수행하십시오.
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
이것에 대한 좋은 점. 결과는 액티브 관계로 반환됩니다 (이 같은 방법을 사용할 수 있도록 last
, count
, where
, pluck
, 등)
보석 find_with_order가 있습니다네이티브 SQL 쿼리를 사용하여 효율적으로 수행 할 수 가 있습니다.
그리고 그것은 Mysql
및 PostgreSQL
.
예를 들면 :
Something.find_with_order(array_of_ids)
관계를 원하는 경우 :
Something.where_with_order(:id, array_of_ids)
내부적 find
으로 id 배열을 사용하면 SELECT
with WHERE id IN...
절이 생성되며 , 이는 id를 반복하는 것보다 더 효율적이어야합니다.
따라서 요청은 데이터베이스에 대한 한 번의 여행에서 충족되지만 절이 SELECT
없는 s ORDER BY
는 정렬되지 않습니다. ActiveRecord는이를 이해하므로 find
다음과 같이 확장 합니다.
Something.find(array_of_ids, :order => 'id')
배열의 ID 순서가 임의적이고 중요한 경우 (즉, 배열에 포함 된 ID 순서에 관계없이 배열과 일치하도록 반환 된 행의 순서를 원하는 경우) 결과를 사후 처리하여 최상의 서버가 될 것이라고 생각합니다. 코드- :order
절을 작성할 수는 있지만 매우 복잡하고 의도를 드러내지 않습니다.
:order => id
)
여기 에 대한 답변을 참조하여
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
Postgresql에서 작동합니다.
find (: order => '...')에는 레코드를 가져올 때이를 수행하는 order 절이 있습니다. 여기에서도 도움을받을 수 있습니다.