Kiedy robisz to Something.find(array_of_ids)
w Railsach, kolejność wynikowej tablicy nie zależy od kolejności array_of_ids
.
Czy jest jakiś sposób, aby znaleźć i zachować zamówienie?
Bankomat I ręcznie sortuję rekordy według kolejności identyfikatorów, ale to trochę kiepskie.
UPD: jeśli możliwe jest określenie kolejności za pomocą :order
parametru i jakiejś klauzuli SQL, to w jaki sposób?
ruby-on-rails
ruby
activerecord
Leonid Shevtsov
źródło
źródło
Odpowiedzi:
Odpowiedź dotyczy tylko mysql
W mysql znajduje się funkcja o nazwie FIELD ()
Oto, jak możesz go użyć w .find ():
Aktualizacja: To zostanie usunięte z kodu źródłowego Rails 6.1 Rails
źródło
FIELDS()
Postgres?Object.where(id: ids).order("field(id, #{ids.join ','})")
Co dziwne, nikt nie zasugerował czegoś takiego:
Tak wydajny, jak to tylko możliwe, oprócz tego, że pozwala na to backendowi SQL.
Edycja: aby poprawić własną odpowiedź, możesz to również zrobić w ten sposób:
#index_by
i#slice
są całkiem przydatnymi dodatkami w ActiveSupport odpowiednio dla tablic i skrótów .źródło
Jak stwierdził Mike Woodhouse w swojej odpowiedzi , dzieje się tak, ponieważ pod maską Railsy używają zapytania SQL z a,
WHERE id IN... clause
aby pobrać wszystkie rekordy w jednym zapytaniu. Jest to szybsze niż pobieranie każdego identyfikatora z osobna, ale jak zauważyłeś, nie zachowuje kolejności pobieranych rekordów.Aby to naprawić, możesz posortować rekordy na poziomie aplikacji zgodnie z oryginalną listą identyfikatorów użytych podczas wyszukiwania rekordu.
Bazując na wielu doskonałych odpowiedziach na sortowanie tablicy według elementów innej tablicy , polecam następujące rozwiązanie:
Lub jeśli potrzebujesz czegoś nieco szybszego (ale prawdopodobnie nieco mniej czytelnego), możesz zrobić to:
źródło
Wydaje się, że to działa dla postgresql ( źródło ) - i zwraca relację ActiveRecord
można rozszerzyć również o inne kolumny, np. o
name
kolumnę, użyjposition(name::text in ?)
...źródło
["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ',']
pełna wersja, która działała dla mnie:scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) }
dzięki @gingerlime @IrishDubGuyJak odpowiedziałem tutaj , właśnie opublikowałem gem ( order_as_specified ), który pozwala na natywne porządkowanie SQL w następujący sposób:
O ile mogłem to przetestować, działa natywnie we wszystkich RDBMS i zwraca relację ActiveRecord, którą można połączyć.
źródło
Nie jest to możliwe w języku SQL, który działałby niestety we wszystkich przypadkach, musiałbyś albo napisać pojedyncze znaleziska dla każdego rekordu lub zamówienia w języku Ruby, chociaż prawdopodobnie istnieje sposób, aby działał przy użyciu zastrzeżonych technik:
Pierwszy przykład:
BARDZO NIEWYDAJNE
Drugi przykład:
źródło
sorted = arr.map { |val| Model.find(val) }
sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
Odpowiedź @Gunchars jest świetna, ale nie działa po wyjęciu z pudełka w Railsach 2.3, ponieważ klasa Hash nie jest uporządkowana. Prostym obejściem jest rozszerzenie klasy Enumerable 'w
index_by
celu użycia klasy OrderedHash:Teraz podejście @Gunchars będzie działać
Premia
Następnie
źródło
Zakładając
Model.pluck(:id)
zwroty[1,2,3,4]
i chcesz zamówić[2,4,1,3]
Koncepcja polega na wykorzystaniu
ORDER BY CASE WHEN
klauzuli SQL. Na przykład:W Railsach możesz to osiągnąć, mając w modelu publiczną metodę konstruowania podobnej struktury:
Następnie wykonaj:
Dobra rzecz w tym. Wyniki są zwracane jako ActiveRecord Relations (co pozwala na stosowanie metod, takich jak
last
,count
,where
,pluck
, etc)źródło
Istnieje gem find_with_order, który pozwala zrobić to wydajnie za pomocą natywnego zapytania SQL.
Obsługuje zarówno
Mysql
iPostgreSQL
.Na przykład:
Jeśli chcesz relacji:
źródło
Pod maską
find
tablica identyfikatorów wygeneruje klauzulęSELECT
zWHERE id IN...
klauzulą, która powinna być bardziej wydajna niż przechodzenie przez pętle identyfikatorów.Tak więc żądanie jest spełnione w jednej podróży do bazy danych, ale wiadomości
SELECT
bezORDER BY
klauzul są nieposortowane. ActiveRecord to rozumie, więc rozszerzamy naszefind
w następujący sposób:Jeśli kolejność identyfikatorów w Twojej tablicy jest dowolna i znacząca (tj. Chcesz, aby kolejność zwracanych wierszy pasowała do Twojej tablicy niezależnie od sekwencji zawartych w niej identyfikatorów), to myślę, że najlepszym serwerem będzie przetwarzanie końcowe wyników w kod - możesz zbudować
:order
klauzulę, ale byłoby to piekielnie skomplikowane i wcale nie ujawniające intencji.źródło
:order => id
)Chociaż nie widzę tego w żadnym miejscu w dzienniku zmian, wygląda na to, że ta funkcjonalność została zmieniona wraz z wydaniem wersji
5.2.0
.Tutaj zatwierdź aktualizację dokumentów oznaczonych tagiem
5.2.0
Jednak wydaje się, że również zostały przeniesione do wersji5.0
.źródło
W nawiązaniu do odpowiedzi tutaj
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
działa dla Postgresql.źródło
W find (: order => '...') znajduje się klauzula order, która robi to podczas pobierania rekordów. Tutaj również możesz uzyskać pomoc.
tekst linku
źródło