W mojej aplikacji Railsowej kilka razy napotkałem problem, który chciałbym wiedzieć, jak rozwiązują go inni ludzie:
Mam pewne rekordy, w których wartość jest opcjonalna, więc niektóre rekordy mają wartość, a niektóre są puste dla tej kolumny.
Jeśli uporządkuję według tej kolumny w niektórych bazach danych, wartości null są sortowane jako pierwsze, a w niektórych bazach danych wartości null są sortowane jako ostatnie.
Na przykład mam zdjęcia, które mogą, ale nie muszą należeć do kolekcji, tj. Są tam zdjęcia, gdzie collection_id=nil
i gdzie collection_id=1
itp.
Jeśli to zrobię, Photo.order('collection_id desc)
w SQLite otrzymuję wartości null jako ostatnie, ale w PostgreSQL najpierw otrzymuję wartości null.
Czy istnieje ładny, standardowy sposób Railsów na to, aby poradzić sobie z tym i uzyskać stałą wydajność w dowolnej bazie danych?
where
nie zwraca tablicy, zwraca ActiveRecord :: Relation, a wymuszenie wyników na tablicy spowoduje, że wszystko, co oczekuje standardowego ActiveRecord :: Relation, zawiedzie (np. Paginacja).Nie jestem ekspertem w SQL, ale dlaczego nie posortować najpierw według tego, czy coś jest zerowe, a następnie posortować według tego, jak chcesz to posortować.
Photo.order('collection_id IS NULL, collection_id DESC') # Null's last Photo.order('collection_id IS NOT NULL, collection_id DESC') # Null's first
Jeśli używasz tylko PostgreSQL, możesz to również zrobić
Photo.order('collection_id DESC NULLS LAST') #Null's Last Photo.order('collection_id DESC NULLS FIRST') #Null's First
Jeśli chcesz czegoś uniwersalnego (na przykład używasz tego samego zapytania w kilku bazach danych, możesz użyć (dzięki uprzejmości @philT)
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
źródło
p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
NULLS LAST
nie pozwala ci to łączyć.last()
się z zapytaniem w Railsach 4.2. Poniżej zamieściłem obejście.IS NULL
wstawianie zerowych wierszy jest ostatnie? Można by pomyśleć, że to postawi ich na pierwszym miejscu.Mimo że jest teraz 2017 r., Nadal nie ma konsensusu, czy
NULL
powinny one mieć pierwszeństwo. Bez wyrażenia tego wprost, wyniki będą się różnić w zależności od DBMS.Aby zilustrować problem, stworzyłem listę kilku najpopularniejszych przypadków, jeśli chodzi o rozwój Railsów:
PostgreSQL
NULL
s mają najwyższą wartość.MySQL
NULL
s mają najniższą wartość.SQLite
NULL
s mają najniższą wartość.Rozwiązanie
Niestety, same Railsy nie zapewniają jeszcze rozwiązania tego problemu.
Specyficzne dla PostgreSQL
W przypadku PostgreSQL możesz całkiem intuicyjnie użyć:
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
Specyficzne dla MySQL
W przypadku MySQL można umieścić znak minus z góry, ale ta funkcja wydaje się nie być udokumentowana. Wydaje się, że działa nie tylko z wartościami liczbowymi, ale także z datami.
Photo.order('-collection_id DESC') # NULLs come last
Specyficzne dla PostgreSQL i MySQL
Aby objąć oba z nich, wydaje się, że działa to:
Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last
Mimo to ten nie działa w SQLite.
Uniwersalne rozwiązanie
Aby zapewnić obsługę krzyżową dla wszystkich DBMS, musiałbyś napisać zapytanie przy użyciu
CASE
, co sugeruje @PhilIT:Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
co przekłada się na pierwsze sortowanie każdego z rekordów najpierw według
CASE
wyników (domyślnie rosnąco, co oznacza, żeNULL
wartości będą ostatnie), a następniecalculation_id
.źródło
Photo.order('collection_id DESC NULLS LAST')
Wiem, że to stary, ale właśnie znalazłem ten fragment i działa dla mnie.
źródło
Umieść znak minus przed nazwa_kolumny i odwróć kolejność. Działa na mysql. Więcej szczegółów
Product.order('something_date ASC') # NULLS came first Product.order('-something_date DESC') # NULLS came last
źródło
Trochę za późno, ale jest na to ogólny sposób SQL. Jak zwykle
CASE
na ratunek.Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
źródło
Najłatwiej jest użyć:
.order('name nulls first')
źródło
last
wydaje się wstawiać błędny DESC.Ze względu na potomność chciałem podkreślić błąd ActiveRecord dotyczący
NULLS FIRST
.Jeśli spróbujesz zadzwonić:
Railsy będą próbowały wywołać
reverse_order.first
, alereverse_order
nie są zgodne zNULLS LAST
, ponieważ próbują wygenerować nieprawidłowy SQL:PG::SyntaxError: ERROR: syntax error at or near "DESC" LINE 1: ...dents" ORDER BY table_column DESC NULLS LAST DESC LIMIT...
Odwołano się do tego kilka lat temu w niektórych wciąż otwartych problemach z Railsami ( jeden , dwa , trzy ). Udało mi się to obejść, wykonując następujące czynności:
scope :nulls_first, -> { order("table_column IS NOT NULL") } scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }
Wygląda na to, że łącząc te dwa zamówienia w łańcuch, generowany jest prawidłowy kod SQL:
Model Load (12.0ms) SELECT "models".* FROM "models" ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1
Jedynym minusem jest to, że to połączenie należy wykonać dla każdego zakresu.
źródło
W moim przypadku potrzebowałem sortować wiersze według daty rozpoczęcia i zakończenia według ASC, ale w kilku przypadkach data_końcowa była pusta i te wiersze powinny znajdować się powyżej, użyłem
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
źródło
Wygląda na to, że musisz to zrobić w Rubim, jeśli chcesz uzyskać spójne wyniki w różnych typach baz danych, ponieważ sama baza danych interpretuje, czy NULLS znajdują się na początku lub na końcu listy.
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}
Ale to nie jest zbyt wydajne.
źródło