51 votes

Trouver les enregistrements du modèle par ID dans l'ordre où le tableau d'ID a été donné.

J'ai une requête pour obtenir les ID des personnes dans un ordre particulier, par exemple : ids = [1, 3, 5, 9, 6, 2]

Je veux ensuite récupérer ces personnes par Person.find(ids)

Mais ils sont toujours récupérés dans l'ordre numérique, je le sais par expérience :

people = Person.find(ids).map(&:id)
 => [1, 2, 3, 5, 6, 9]

Comment puis-je exécuter cette requête de manière à ce que l'ordre soit le même que celui du tableau des identifiants ?

J'ai rendu cette tâche plus difficile car je ne voulais exécuter la requête pour récupérer les personnes qu'une seule fois, à partir des ID donnés. Il est donc hors de question d'effectuer plusieurs requêtes.

J'ai essayé quelque chose comme :

ids.each do |i|
  person = people.where('id = ?', i)

Mais je ne pense pas que cela fonctionne.

38voto

Brian Underwood Points 9730

Note sur ce code :

ids.each do |i|
  person = people.where('id = ?', i)

Il y a deux problèmes :

D'abord, la méthode #each renvoie le tableau sur lequel elle a itéré, donc vous ne récupérez que les identifiants. Ce que vous voulez, c'est un collect

Deuxièmement, le where renverra un objet Arel::Relation, qui au final sera évalué comme un tableau. Vous vous retrouverez donc avec un tableau de tableaux. Vous pouvez résoudre le problème de deux façons.

La première façon serait d'aplatir :

ids.collect {|i| Person.where('id => ?', i) }.flatten

Une version encore meilleure :

ids.collect {|i| Person.where(:id => i) }.flatten

Une deuxième façon serait de faire simplement une recherche :

ids.collect {|i| Person.find(i) }

C'est simple et agréable.

Cependant, vous constaterez que tous ces systèmes effectuent une requête pour chaque itération, ce qui n'est pas très efficace.

J'aime la solution de Sergio, mais en voici une autre que j'aurais suggérée :

people_by_id = Person.find(ids).index_by(&:id) # Gives you a hash indexed by ID
ids.collect {|id| people_by_id[id] }

Je jure que je me souviens qu'ActiveRecord faisait ce classement par ID pour nous. Peut-être que ça a disparu avec Arel ;)

16voto

apeiros Points 898

D'après ce que je vois, vous pouvez soit faire correspondre les ID, soit trier le résultat. Dans ce dernier cas, il existe déjà des solutions, bien que je les trouve inefficaces.

Cartographie des identifiants :

ids = [1, 3, 5, 9, 6, 2]
people_in_order = ids.map { |id| Person.find(id) }

Notez que cela entraînera l'exécution de plusieurs requêtes, ce qui est potentiellement inefficace.

Trier le résultat :

ids = [1, 3, 5, 9, 6, 2]
id_indices = Hash[ids.map.with_index { |id,idx| [id,idx] }] # requires ruby 1.8.7+
people_in_order = Person.find(ids).sort_by { |person| id_indices[person.id] }

Ou, en développant la réponse de Brian Underwoods :

ids = [1, 3, 5, 9, 6, 2]
indexed_people = Person.find(ids).index_by(&:id) # I didn't know this method, TIL :)
people_in_order = indexed_people.values_at(*ids)

J'espère que cela vous aidera.

14voto

Sandip Ransing Points 2344

Si vous avez ids array alors c'est aussi simple que - Person.where(id: ids).sort_by {|p| ids.index(p.id) } OU

persons = Hash[ Person.where(id: ids).map {|p| [p.id, p] }] ids.map {|i| persons[i] }

10voto

elliotcm Points 629

Avec Rails 5, j'ai constaté que cette approche fonctionne (avec postgres, au moins), même pour les requêtes scopées, utiles pour travailler avec ElasticSearch :

Person.where(country: "France").find([3, 2, 1]).map(&:id)
=> [3, 2, 1]

Notez que l'utilisation de where au lieu de find ne préserve pas l'ordre.

Person.where(country: "France").where(id: [3, 2, 1]).map(&:id)
=> [1, 2, 3]

9voto

activars Points 616

Il y a deux façons d'obtenir des entrées en donnant un tableau d'identifiants. Si vous travaillez sur Rails 4, la méthode dynamique est dépréciée, vous devez regarder la solution spécifique à Rails 4 ci-dessous.

Première solution :

Person.find([1,2,3,4])

Cela permettra d'augmenter ActiveRecord::RecordNotFound si aucun enregistrement n'existe

Solution 2 [Rails 3 uniquement] :

Person.find_all_by_id([1,2,3,4])

Cela ne provoquera pas d'exception, mais renverra simplement un tableau vide si aucun enregistrement ne correspond à votre requête.

En fonction de vos besoins, choisissez la méthode que vous souhaitez utiliser ci-dessus, puis triez-les en fonction des critères suivants ids

ids = [1,2,3,4]
people = Person.find_all_by_id(ids)
# alternatively: people = Person.find(ids)
ordered_people = ids.collect {|id| people.detect {|x| x.id == id}}

Solution [Rails 4 uniquement] :

Je pense que Rails 4 offre une meilleure solution.

# without eager loading
Person.where(id: [1,2,3,4]).order('id DESC')

# with eager loading.
# Note that you can not call deprecated `all`
Person.where(id: [1,2,3,4]).order('id DESC').load

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X