Propre façon de trouver ActiveRecord objets par id dans l'ordre spécifié
Je veux obtenir un tableau de ActiveRecord objets donné un tableau d'id.
Je suppose que
Object.find([5,2,3])
Serait de retourner un tableau avec l'objet 5, de l'objet 2, puis l'objet 3 dans cet ordre, mais au lieu de cela je obtenir un tableau ordonné que l'objet 2 objet 3 et puis l'objet 5.
La ActiveRecord Base trouver la méthode de l'API mentionne qu'il ne faut pas s'attendre, dans l'ordre fourni (d'autres documents n'a pas cet avertissement).
Une solution potentielle a été donné dans Trouver par tableau d'id dans le même ordre?, mais de l'ordre d'option ne semble pas être valide pour SQLite.
Je peux écrire du code ruby pour trier les objets par moi-même (que ce soit quelque chose de simple et peu mise à l'échelle ou de la meilleure mise à l'échelle et plus complexe), mais est-il Une Meilleure Façon?
- Où sont les id en venir? Si c'est l'INTERFACE utilisateur (via un utilisateur de les sélectionner), puis mise à l'échelle ne devrait pas être un problème, c'est que l'utilisateur est peu probable de passer du temps à la sélection de plus de 1000 id). Si c'est la base de données (par exemple, à partir d'une table de jointure), pourriez-vous le magasin de l'ordre dans la table de jointure et la question en se basant sur qui?
- Il ressemble à ce qui est n'est plus vrai dans les Rails 5.
Vous devez vous connecter pour publier un commentaire.
Ce n'est pas que MySQL et d'autres DBs arranger les choses sur leur propre, c'est qu'ils n'ont pas les trier. Lorsque vous appelez
Model.find([5, 2, 3])
, le SQL généré est quelque chose comme:Cela n'indique pas un ordre, juste le jeu d'enregistrements que vous voulez retourner. Il s'avère que, généralement, MySQL retournera la base de données rangées dans
'id'
ordre, mais il n'y a aucune garantie de ce.La seule façon d'obtenir la base de données de retour d'enregistrements dans un ordre garanti est d'ajouter une clause order. Si vos dossiers seront toujours retourné dans un ordre particulier, vous pouvez ajouter une colonne de tri de la base de données et ne
Model.find([5, 2, 3], :order => 'sort_column')
. Si ce n'est pas le cas, vous aurez à faire le tri dans le code:Basé sur mon commentaire précédent à Jeroen van Dijk, vous pouvez le faire plus efficacement et en deux lignes à l'aide de
each_with_object
Pour la référence ici est la référence que j'ai utilisé
Maintenant l'autre
Mise à jour
Vous pouvez le faire dans la plupart à l'aide de l'ordre et de cas relevés, ici est une méthode de classe que vous pouvez utiliser.
Apparemment, mySQL et d'autres DB, système de gestion de la sorte les choses sur leur propre. Je pense que vous pouvez ignorer ce fait :
Object.where(id: ids).order("field(id, #{ids.join ','})")
Object.where(id: ids).order(ids.present? && "field(id, #{ids.join ','})")
. Cela permet d'éviter une erreur SQL si l'Id de tableau est vide ou le néant.Un portable solution serait d'utiliser un SQL instruction de CAS dans votre COMMANDE PAR. Vous pouvez utiliser à peu près n'importe quelle expression dans un ORDRE et un CAS peut être utilisé comme un inline table de recherche. Par exemple, le SQL, vous êtes après ressemblerait à ceci:
C'est assez facile de générer avec un peu de Ruby:
Ci-dessus suppose que vous travaillez avec des chiffres ou certains autres valeurs sûres dans
ids
; si ce n'est pas le cas, alors vous voulez les utiliserconnexion.citation
ou l'un des ActiveRecord SQL désinfectant méthodes de bien indiquer votreids
.Puis utilisez le
order
chaîne en tant que votre commande de condition:ou dans le monde moderne:
C'est un peu verbeux, mais il devrait fonctionner de la même avec toute la base de données SQL et il n'est pas difficile de cacher la laideur.
Object.order('case id ' + ids.each_with_index.map { |id, i| "when #{id} then #{i}" }.join(' ') + ' end')
sanitize_sql_array(["when ? then ? ", id, i])
(fonctionne à l'intérieur d'un modèle AR)ids
contenus des numéros, des hypothèses sont une mauvaise idée (doublement lorsqu'elles ne sont pas explicites) donc j'ai espérons informés de cela.Que j'ai répondu à ici, je viens de publier un gem ( order_as_specified ), qui permet de faire SQL natif de commande comme ceci:
Viens de tester et cela fonctionne dans SQLite.
Justin Weiss a écrit un article de blog sur ce problème juste il y a deux jours.
Il semble être une bonne approche pour raconter la base de données sur l'ordre de préférence et de charger tous les enregistrements triés dans l'ordre directement à partir de la base de données. Exemple de son article du blog:
Qui permet d'écrire:
id
. Serait agréable d'être en mesure de déterminer de façon dynamique l'attribut order by, commeuid
ou quelque chose.field_name
de la méthode et de l'utilisation quefield_name
au lieu de"CASE id"
comme ceci:"CASE #{field_name} "
find_ordered_by_uid(uids)
mais c'est juste la construction de ce qu'on dit. Des acclamations.find_by_ordered_ids
pour être plus explicite, et correspondre au nom du fichier de module.id
qui n'est pas unInteger
alors assurez-vous d'envelopper#{id}
avec la parenthèse, comme si " "QUAND" #{uid} " PUIS #{index} ". Ne peux pas vous remercier assez de @spickermann pour ce petit bout de code fonctionne très bien!sanitize_sql_array(["when ? then ? ", id, index])
dans un modèle AR.return where('null') if ids.blank?
et il va retourner un videActiveRecord::Relation
quandids
est un Tableau vide.none
portée au lieu dewhere('null')
.order_clause = "CASE #{self.table_name}.id "
à la place.Voici un performant (de hachage de recherche, pas de O(n) de la matrice de la recherche comme dans les détecter!) one-liner, en tant que méthode:
L'autre (probablement la plus efficace) de manière à faire en Ruby:
result
) à la fin de votre bloc. Il simplifie le bâtiment hachages à mon humble avis.Voici la chose la plus simple que je pouvais venir:
C'est probablement la façon la plus simple, en supposant que vous n'avez pas trop d'objets à trouver, car il nécessite un voyage à la base de données pour chaque id.