web-dev-qa-db-de.com

Finden Sie Modelldatensätze nach ID in der Reihenfolge, in der das ID-Array angegeben wurde

Ich habe eine Abfrage, um die IDs von Personen in einer bestimmten Reihenfolge abzurufen. Sagen Sie: ids = [1, 3, 5, 9, 6, 2]

Ich möchte diese Leute dann per Person.find(ids) abholen.

Sie werden jedoch immer in numerischer Reihenfolge abgerufen. Ich weiß dies, indem sie Folgendes ausführt:

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

Wie kann ich diese Abfrage so ausführen, dass die Reihenfolge mit der Reihenfolge des ids-Arrays übereinstimmt? 

Ich habe diese Aufgabe schwieriger gemacht, da ich die Abfrage nur ausführen wollte, um die Personen nur einmal von den angegebenen IDs abzurufen. Es ist also nicht möglich, mehrere Abfragen auszuführen.

Ich habe so etwas ausprobiert:

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

Aber ich glaube nicht, dass das funktioniert.

33
Jonathan

Hinweis zu diesem Code:

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

Es gibt zwei Probleme damit:

Erstens gibt die #each-Methode das Array zurück, auf dem sie iteriert wurde, sodass Sie die IDs einfach zurückbekommen. Was Sie wollen, ist ein Sammeln

Zweitens gibt das Where ein Arel :: Relation-Objekt zurück, das am Ende als Array ausgewertet wird. Sie würden also mit einer Reihe von Arrays enden. Es gibt zwei Möglichkeiten.

Der erste Weg wäre das Abflachen:

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

Noch bessere Version:

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

Eine zweite Möglichkeit wäre, einfach eine Suche zu machen:

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

Das ist schön und einfach

Sie werden jedoch feststellen, dass diese alle für jede Iteration eine Abfrage durchführen, also nicht sehr effizient.

Ich mag Sergios Lösung, aber hier wäre eine andere, die ich vorgeschlagen hätte:

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

Ich schwöre, dass ich mich daran erinnere, dass ActiveRecord diese ID-Bestellung für uns durchgeführt hat. Vielleicht ging es mit Arel weg;)

30
Brian Underwood

Es gibt zwei Möglichkeiten, Einträge über ein Array von IDs abzurufen. Wenn Sie mit Rails 4 arbeiten, die dynamische Methode ist veraltet, müssen Sie sich die spezifische Lösung für Rails 4 unten anschauen. 

Lösung eins:

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

Dies führt zu ActiveRecord::RecordNotFound, wenn kein Datensatz vorhanden ist

Lösung zwei [nur Rails 3]:

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

Dies führt nicht zu einer Ausnahme. Geben Sie einfach ein leeres Array zurück, wenn kein Datensatz mit Ihrer Abfrage übereinstimmt.

Basierend auf Ihrer Anforderung, wählen Sie die Methode aus, die Sie oben verwenden möchten, und sortiert sie nach 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}}

Lösung [nur Rails 4]:

Ich denke, Rails 4 bietet eine bessere Lösung.

# 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
13
activars

So wie ich es sehe, können Sie entweder die IDs zuordnen oder das Ergebnis sortieren. Für letztere gibt es bereits Lösungen, die ich für ineffizient halte.

IDs zuordnen:

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

Beachten Sie, dass dies dazu führt, dass mehrere Abfragen ausgeführt werden, was möglicherweise ineffizient ist.

Ergebnis sortieren:

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] }

Oder erweitern Sie die Antwort von 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)

Hoffentlich hilft das

10
apeiros

Wenn Sie ids array haben, ist es so einfach wie - Person.where(id: ids).sort_by {|p| ids.index(p.id) } .__ 

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

6
Sandip Ransing

Alte Frage, aber die Sortierung kann durch Sortieren mit der SQL-Funktion FIELD erfolgen. (Nur mit MySQL getestet.)

In diesem Fall sollte so etwas funktionieren:

Person.order(Person.send(:sanitize_sql_array, ['FIELD(id, ?)', ids])).find(ids)

Welche Ergebnisse ergeben sich in der folgenden SQL:

SELECT * FROM people
  WHERE id IN (1, 3, 5, 9, 6, 2)
  ORDER BY FIELD(id, 1, 3, 5, 9, 6, 2)
5
Koen.

Sie können Benutzer nach id asc aus der Datenbank sortieren und sie in der Anwendung nach Belieben neu anordnen. Überprüfen Sie dies heraus:

ids = [1, 3, 5, 9, 6, 2]
users = ids.sort.map {|i| {id: i}} # Or User.find(ids) or another query

# users sorted by id asc (from the query)
users # => [{:id=>1}, {:id=>2}, {:id=>3}, {:id=>5}, {:id=>6}, {:id=>9}]

users.sort_by! {|u| ids.index u[:id]} 

# users sorted as you wanted
users # => [{:id=>1}, {:id=>3}, {:id=>5}, {:id=>9}, {:id=>6}, {:id=>2}]

Der Trick besteht darin, das Array nach einem künstlichen Wert zu sortieren: Index der Objekt-ID in einem anderen Array.

4

Bei den meisten anderen Lösungen können Sie die resultierende Abfrage nicht weiter filtern. Aus diesem Grund mag ich Koens Antwort

Ähnlich wie diese Antwort, aber für Postgres füge ich diese Funktion meinem ApplicationRecord (Rails 5+) oder jedem Modell (Rails 4) hinzu:

def self.order_by_id_list(id_list)
  values_clause = id_list.each_with_index.map{|id, i| "(#{id}, #{i})"}.join(", ")
  joins("LEFT JOIN (VALUES #{ values_clause }) AS #{ self.table_name}_id_order(id, ordering) ON #{ self.table_name }.id = #{ self.table_name }_id_order.id")
    .order("#{ self.table_name }_id_order.ordering")
end

Die Abfrage-Lösung stammt aus dieser Frage .

2
Jerph

Wenn Sie MySQL verwenden, ist dies möglicherweise eine einfache Lösung für Sie.

Post.where(sky: 'blue').order("FIELD(sort_item_field_id, 2,5,1,7,3,4)")
1
Eskim0

Mit Rails 5 habe ich herausgefunden, dass dieser Ansatz (zumindest mit Postgres) funktioniert, auch für Abfragen mit einem bestimmten Umfang.

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

Beachten Sie, dass die Verwendung von where anstelle von find die Reihenfolge nicht beibehält.

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