update rails query left includes has_and_belongs_to_many create active ruby-on-rails ruby activerecord

ruby on rails - rails - ActiveRecord.find(array_of_ids), preservando el orden



rails sql query (10)

Cuando realiza Something.find(array_of_ids) en Rails, el orden de la matriz resultante no depende del orden de array_of_ids .

¿Hay alguna manera de hacer el hallazgo y preservar el orden?

ATM: ordeno manualmente los registros en función del orden de los ID, pero eso es una especie de cojera.

UPD: si es posible especificar el orden usando :order param y algún tipo de cláusula SQL, ¿cómo?


Aunque no veo que se mencione en ningún lugar en un CHANGELOG, parece que esta funcionalidad se modificó con el lanzamiento de la versión 5.2.0 .

Aquí se commit actualizar los documentos etiquetados con 5.2.0 Sin embargo, parece que también se ha backported a la versión 5.0 .


Como declaró en su respuesta , esto ocurre porque, bajo el capó, Rails está utilizando una consulta SQL con una WHERE id IN... clause para recuperar todos los registros en una consulta. Esto es más rápido que recuperar cada identificación individualmente, pero como notó, no conserva el orden de los registros que está recuperando.

Para solucionar esto, puede ordenar los registros en el nivel de aplicación de acuerdo con la lista original de ID que utilizó al buscar el registro.

Basándome en las muchas respuestas excelentes para Ordenar una matriz según los elementos de otra matriz , recomiendo la siguiente solución:

Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}

O si necesita algo un poco más rápido (pero posiblemente algo menos legible) puede hacer esto:

Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)


Como respondí here , acabo de lanzar una gema ( order_as_specified ) que le permite hacer un ordenamiento SQL nativo como este:

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

Por lo que he podido probar, funciona de forma nativa en todos los RDBMS, y devuelve una relación de ActiveRecord que se puede encadenar.


Curiosamente, nadie ha sugerido algo como esto:

index = Something.find(array_of_ids).group_by(&:id) array_of_ids.map { |i| index[i].first }

Tan eficiente como se obtiene además de permitir que SQL back-end lo haga.

Editar: Para mejorar mi propia respuesta, también puede hacerlo así:

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

#index_by y #slice son #slice bastante útiles en ActiveSupport para matrices y hash, respectivamente.


Debajo del capó, find con una serie de identificadores generará un SELECT con una cláusula WHERE id IN... que debería ser más eficiente que recorrer los identificadores.

De modo que la solicitud se satisface en un viaje a la base de datos, pero los SELECT s sin las cláusulas ORDER BY están desordenadas. ActiveRecord entiende esto, por lo que ampliamos nuestro find siguiente manera:

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

Si el orden de los identificadores en su matriz es arbitrario y significativo (es decir, desea que el orden de las filas devueltas coincida con su matriz, independientemente de la secuencia de identificadores que contenga), creo que sería mejor servidor procesando los resultados en código: podrías construir una cláusula :order pero sería diabólicamente complicado y para nada revelador de intenciones.


Esto parece funcionar para postgresql ( source ) - y devuelve una relación 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])

puede extenderse también para otras columnas, por ejemplo, para la columna de name , usar position(name::text in ?) ...


Hay una cláusula de orden en find (: order => ''...'') que hace esto al recuperar registros. Puedes obtener ayuda de aquí también.

Texto del enlace


La respuesta de @Gunchars es genial, pero no funciona de fábrica en Rails 2.3 porque la clase Hash no está ordenada. Una solución simple es extender la clase Enumerable '' index_by para usar la clase 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

Ahora el enfoque de @Gunchars funcionará

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

Prima

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

Entonces

Something.find_with_relevance(array_of_ids)


No es posible en SQL que funcione en todos los casos, desafortunadamente, necesitaría escribir hallazgos individuales para cada registro u orden en ruby, aunque probablemente haya una manera de hacerlo funcionar usando técnicas patentadas:

Primer ejemplo:

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

MUY INEFICIENTE

Segundo ejemplo:

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


La respuesta es solo para mysql

Hay una función en mysql llamada FIELD()

Aquí es cómo puedes usarlo en .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]