Quando você faz Something.find(array_of_ids)
no Rails, a ordem do array resultante não depende da ordem de array_of_ids
.
Existe alguma maneira de fazer a localização e preservar a ordem?
ATM Eu classifico manualmente os registros com base na ordem dos IDs, mas isso é meio chato.
UPD: se for possível especificar a ordem usando o :order
parâmetro e algum tipo de cláusula SQL, como?
ruby-on-rails
ruby
activerecord
Leonid Shevtsov
fonte
fonte
Respostas:
A resposta é apenas para mysql
Existe uma função no mysql chamada FIELD ()
Aqui está como você pode usá-lo em .find ():
Atualização: Isto será removido no código fonte do Rails 6.1 Rails
fonte
FIELDS()
em Postgres?Object.where(id: ids).order("field(id, #{ids.join ','})")
Estranhamente, ninguém sugeriu algo assim:
Tão eficiente quanto possível, além de permitir que o backend SQL faça isso.
Edit: Para melhorar minha própria resposta, você também pode fazer assim:
#index_by
e#slice
são adições muito úteis no ActiveSupport para matrizes e hashes, respectivamente.fonte
Como Mike Woodhouse afirmou em sua resposta , isso ocorre porque, nos bastidores, o Rails está usando uma consulta SQL com um
WHERE id IN... clause
para recuperar todos os registros em uma consulta. Isso é mais rápido do que recuperar cada id individualmente, mas como você percebeu, não preserva a ordem dos registros que está recuperando.Para corrigir isso, você pode classificar os registros no nível do aplicativo de acordo com a lista original de IDs que você usou ao pesquisar o registro.
Com base nas muitas respostas excelentes para classificar uma matriz de acordo com os elementos de outra matriz , recomendo a seguinte solução:
Ou se você precisar de algo um pouco mais rápido (mas indiscutivelmente um pouco menos legível), você pode fazer isso:
fonte
Isso parece funcionar para postgresql ( fonte ) - e retorna uma relação ActiveRecord
pode ser estendido para outras colunas também, por exemplo, para a
name
coluna, useposition(name::text in ?)
...fonte
["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ',']
versão completa que funcionou para mim:scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) }
obrigado @gingerlime @IrishDubGuyComo respondi aqui , acabei de lançar uma gema ( order_as_specified ) que permite que você faça uma ordenação SQL nativa como esta:
Pelo que pude testar, ele funciona nativamente em todos os RDBMSs e retorna uma relação ActiveRecord que pode ser encadeada.
fonte
Não é possível em SQL que funcione em todos os casos, infelizmente, você precisaria escrever achados únicos para cada registro ou pedido em ruby, embora provavelmente haja uma maneira de fazê-lo funcionar usando técnicas proprietárias:
Primeiro exemplo:
MUITO INEFICIENTE
Segundo exemplo:
fonte
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
A resposta de @Gunchars é ótima, mas não funciona fora da caixa no Rails 2.3 porque a classe Hash não é ordenada. Uma solução simples é estender a classe Enumerable '
index_by
para usar a classe OrderedHash:Agora a abordagem de @Gunchars funcionará
Bônus
Então
fonte
Assumindo
Model.pluck(:id)
retornos[1,2,3,4]
e você deseja o pedido de[2,4,1,3]
O conceito é utilizar a
ORDER BY CASE WHEN
cláusula SQL. Por exemplo:No Rails, você pode conseguir isso tendo um método público em seu modelo para construir uma estrutura semelhante:
Então faça:
A coisa boa sobre isso. Os resultados são retornados como Relações ActiveRecord (permitindo que você use métodos como
last
,count
,where
,pluck
, etc)fonte
Há uma joia find_with_order que permite que você faça isso de forma eficiente usando uma consulta SQL nativa.
E suporta ambos
Mysql
ePostgreSQL
.Por exemplo:
Se você quer relação:
fonte
Sob o capô,
find
com uma matriz de ids irá gerar umSELECT
com umWHERE id IN...
cláusula, que deve ser mais eficiente do que repetir os ids.Portanto, a solicitação é atendida em uma viagem ao banco de dados, mas
SELECT
s semORDER BY
cláusulas não são classificados. ActiveRecord entende isso, então expandimos nosso dafind
seguinte forma:Se a ordem dos ids em sua matriz for arbitrária e significativa (ou seja, você deseja que a ordem das linhas retornadas corresponda à sua matriz, independentemente da sequência de ids contida nela), então acho que você seria o melhor servidor pós-processamento dos resultados em código - você poderia construir uma
:order
cláusula, mas seria terrivelmente complicado e não revelaria nenhuma intenção.fonte
:order => id
)Embora eu não veja isso mencionado em nenhum CHANGELOG, parece que essa funcionalidade foi alterada com o lançamento da versão
5.2.0
.Aqui, confirme a atualização dos documentos marcados com
5.2.0
No entanto, parece que também foi feito o backport para a versão5.0
.fonte
Com referência à resposta aqui
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
funciona para Postgresql.fonte
Há uma cláusula de pedido em find (: pedido => '...') que faz isso ao buscar registros. Você também pode obter ajuda aqui.
Texto do link
fonte