Lorsque vous faites Something.find(array_of_ids)
dans Rails, l'ordre du tableau résultant ne dépend pas de l'ordre de array_of_ids
.
Existe-t-il un moyen de rechercher et de conserver la commande?
ATM Je trie manuellement les enregistrements en fonction de l'ordre des ID, mais c'est un peu nul.
UPD: s'il est possible de spécifier l'ordre en utilisant le :order
paramètre et une sorte de clause SQL, alors comment?
ruby-on-rails
ruby
activerecord
Léonid Chevtsov
la source
la source
Réponses:
La réponse est pour mysql uniquement
Il y a une fonction dans mysql appelée FIELD ()
Voici comment vous pouvez l'utiliser dans .find ():
Mise à jour: cela sera supprimé dans le code source de Rails 6.1 Rails
la source
FIELDS()
Postgres?Object.where(id: ids).order("field(id, #{ids.join ','})")
Curieusement, personne n'a suggéré quelque chose comme ceci:
Aussi efficace que possible, en plus de laisser le backend SQL le faire.
Edit: Pour améliorer ma propre réponse, vous pouvez également le faire comme ceci:
#index_by
et#slice
sont des ajouts très pratiques dans ActiveSupport pour les tableaux et les hachages respectivement.la source
Comme Mike Woodhouse l'a déclaré dans sa réponse , cela se produit parce que, sous le capot, Rails utilise une requête SQL avec un
WHERE id IN... clause
pour récupérer tous les enregistrements en une seule requête. C'est plus rapide que de récupérer chaque identifiant individuellement, mais comme vous l'avez remarqué, cela ne préserve pas l'ordre des enregistrements que vous récupérez.Pour résoudre ce problème, vous pouvez trier les enregistrements au niveau de l'application en fonction de la liste d'origine des ID que vous avez utilisés lors de la recherche de l'enregistrement.
Sur la base des nombreuses excellentes réponses pour trier un tableau en fonction des éléments d'un autre tableau , je recommande la solution suivante:
Ou si vous avez besoin de quelque chose d'un peu plus rapide (mais sans doute un peu moins lisible), vous pouvez le faire:
la source
Cela semble fonctionner pour postgresql ( source ) - et renvoie une relation ActiveRecord
peut également être étendu à d'autres colonnes, par exemple pour
name
colonne, utilisezposition(name::text in ?)
...la source
["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ',']
version complète qui a fonctionné pour moi:scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) }
merci @gingerlime @IrishDubGuyComme je l'ai répondu ici , je viens de publier un gem ( order_as_specified ) qui vous permet de faire un ordre SQL natif comme ceci:
Autant que j'ai pu tester, cela fonctionne de manière native dans tous les SGBDR, et il renvoie une relation ActiveRecord qui peut être chaînée.
la source
Pas possible dans SQL qui fonctionnerait dans tous les cas, malheureusement, vous auriez soit besoin d'écrire des recherches uniques pour chaque enregistrement, soit de commander en ruby, bien qu'il existe probablement un moyen de le faire fonctionner en utilisant des techniques propriétaires:
Premier exemple:
TRÈS INEFFICACE
Deuxième exemple:
la source
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
La réponse @Gunchars est excellente, mais elle ne fonctionne pas directement dans Rails 2.3 car la classe Hash n'est pas ordonnée. Une solution de contournement simple consiste à étendre la classe Enumerable '
index_by
pour utiliser la classe OrderedHash:L'approche de @Gunchars fonctionnera maintenant
Prime
ensuite
la source
En supposant des
Model.pluck(:id)
retours[1,2,3,4]
et que vous voulez l'ordre de[2,4,1,3]
Le concept consiste à utiliser la
ORDER BY CASE WHEN
clause SQL. Par exemple:Dans Rails, vous pouvez y parvenir en ayant une méthode publique dans votre modèle pour construire une structure similaire:
Alors fais:
La bonne chose à ce sujet. Les résultats sont renvoyés comme des relations ActiveRecord (vous permettant d'utiliser des méthodes telles que
last
,count
,where
,pluck
, etc.)la source
Il y a une gemme find_with_order qui vous permet de le faire efficacement en utilisant une requête SQL native.
Et il prend en charge à la fois
Mysql
etPostgreSQL
.Par exemple:
Si vous voulez une relation:
la source
Sous le capot,
find
avec un tableau d'id généreraSELECT
uneWHERE id IN...
clause with a , ce qui devrait être plus efficace que de parcourir les ID.La requête est donc satisfaite en un seul voyage vers la base de données, mais les
SELECT
s sansORDER BY
clauses ne sont pas triées. ActiveRecord comprend cela, nous développons donc notrefind
comme suit:Si l'ordre des identifiants dans votre tableau est arbitraire et significatif (c'est-à-dire que vous voulez que l'ordre des lignes renvoyées corresponde à votre tableau indépendamment de la séquence d'identifiants qu'il contient), je pense que vous seriez le meilleur serveur en post-traitant les résultats dans code - vous pourriez construire une
:order
clause mais ce serait diaboliquement compliqué et pas du tout révélateur d'intention.la source
:order => id
)Bien que je ne le vois pas mentionné nulle part dans un CHANGELOG, il semble que cette fonctionnalité ait été modifiée avec la sortie de la version
5.2.0
.Ici, commettez la mise à jour des documents étiquetés avec
5.2.0
Cependant, il semble avoir également été rétroporté dans la version5.0
.la source
En référence à la réponse ici
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
fonctionne pour Postgresql.la source
Il y a une clause order dans find (: order => '...') qui fait cela lors de la récupération des enregistrements. Vous pouvez également obtenir de l'aide d'ici.
texte du lien
la source