Rails: Замовлення з нулями останнє


83

У моєму додатку Rails я кілька разів стикався з проблемою, яку я хотів би знати, як вирішують інші люди:

У мене є певні записи, де значення є необов’язковим, тому деякі записи мають значення, а деякі є нульовими для цього стовпця.

Якщо я впорядкую за цим стовпцем у деяких базах даних, нулі сортуються першими, а в деяких базах даних нулі сортуються останніми.

Наприклад, у мене є фотографії, які можуть належати або не належати до колекції, тобто є деякі фотографії де, collection_id=nilа де де collection_id=1тощо.

Якщо я це роблю, Photo.order('collection_id desc)тоді на SQLite я отримую нулі в останню чергу, але на PostgreSQL я отримую нулі в першу чергу.

Чи є хороший, стандартний спосіб Rails впоратися з цим і отримати стабільну продуктивність у будь-якій базі даних?

Відповіді:


0

Додавання масивів разом збереже порядок:

@nonull = Photo.where("collection_id is not null").order("collection_id desc")
@yesnull = Photo.where("collection_id is null")
@wanted = @nonull+@yesnull

http://www.ruby-doc.org/core/classes/Array.html#M000271


2
Ну, я не люблю цю ідею, але, думаю, це спрацювало б. Вибачте, що так довго залишав його відкритим, я сподівався, що з’являться інші відповіді. Хоча, витративши ще трохи часу на роздуми над цим, я думаю, це можна перетворити на метод на фотомоделі, і тоді це буде не надто погано.
Ендрю

Це легко, якщо ви використовуєте mysql. Дивіться моє рішення.
Jacob

Правильно, я мав би згадати, що мій шлях був лише найбільш агностичним способом, який я міг знайти.
Ерік

8
Це погана ідея, оскільки whereне повертає масив, він повертає ActiveRecord :: Relation і примушуючи результати до масиву призведе до того, що все, що очікує відмови стандартного ActiveRecord :: Relation (наприклад, пагінація).
Mike Bethany

1
Правда, хоча, схоже, доступні методи або вириваються з AR, або не є (повністю) портативними.
Ерік

271

Я не фахівець у SQL, але чому б просто не відсортувати, якщо спочатку щось є нульовим, а потім відсортувати за тим, як ви хотіли це сортувати.

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

Якщо ви використовуєте лише PostgreSQL, ви також можете це зробити

Photo.order('collection_id DESC NULLS LAST')  #Null's Last
Photo.order('collection_id DESC NULLS FIRST') #Null's First

Якщо ви хочете щось універсальне (наприклад, ви використовуєте один і той же запит у кількох базах даних, ви можете використовувати (люб'язно надано @philT)

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')

22
+1, набагато кращий за прийняту відповідь і може бути висловлений через Arel
m_x

1
вау, це старий коментар! Тож я здогадуюсь, що я намагався сказати:p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
m_x

2
Зверніть увагу, що NULLS LASTце не дозволяє вам прив’язуватись .last()до вашого запиту станом на Rails 4.2. Я опублікував обхід нижче.
Ленні Бозе

1
Чому впорядкування шляхом IS NULLрозміщення рядків NULL останнє? Можна подумати, що це поставить їх на перше місце.
jackocnr

2
@jackocr myguess: IS NULL обчислюється як 1 якщо true, 0 якщо false, сортується, 0 приходить до 1 ...
Intentss

37

Незважаючи на те, що зараз 2017 рік, все ще не було досягнуто консенсусу щодо того, чи NULLслід мати пріоритет. Без того, що ви чітко про це заявляєте, ваші результати будуть змінюватися залежно від СУБД.

Стандарт не визначає, як слід упорядковувати NULL у порівнянні зі значеннями, що не є NULL, за винятком того, що будь-які два NULL слід вважати однаково упорядкованими, і що NULL повинні сортувати або вище, або нижче всіх значень, що не є NULL.

джерело, порівняння більшості СУБД

Щоб проілюструвати проблему, я склав список кількох найпопулярніших випадків, коли мова йде про розробку Rails:

PostgreSQL

NULLs мають найвище значення.

За замовчуванням нульові значення сортуються так, ніби більші за будь-які ненульові значення.

джерело: документація PostgreSQL

MySQL

NULLs мають найменше значення.

При виконанні ORDER BY, значення NULL відображаються спочатку, якщо ви робите ORDER BY ... ASC, і останнє, якщо ви робите ORDER BY ... DESC.

джерело: документація MySQL

SQLite

NULLs мають найменше значення.

Рядок зі значенням NULL є вищим, ніж рядки з регулярними значеннями у порядку зростання, і він змінюється на спадний.

джерело

Рішення

На жаль, сама Rails поки не пропонує рішення.

Специфічні для PostgreSQL

Для PostgreSQL ви можете цілком інтуїтивно використовувати:

Photo.order('collection_id DESC NULLS LAST') # NULLs come last

Специфічний для MySQL

Для MySQL ви можете заздалегідь поставити знак мінус, проте ця функція, здається, не оформлена документально. Здається, це працює не тільки з числовими значеннями, але і з датами.

Photo.order('-collection_id DESC') # NULLs come last

Специфічні для PostgreSQL та MySQL

Щоб охопити їх обох, це, здається, працює:

Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last

Тим не менше, цей не працює в SQLite.

Універсальне рішення

Щоб забезпечити перехресну підтримку всіх СУБД, вам доведеться написати запит, використовуючи CASEвже запропонований @PhilIT:

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')

що перекладається на перше сортування кожного із записів спочатку за CASEрезультатами (за замовчуванням за зростанням, що означає, що NULLзначення будуть останніми), друге за calculation_id.


14
Photo.order('collection_id DESC NULLS LAST')

Я знаю, що це старий, але я щойно знайшов цей фрагмент, і він працює для мене.


Я не знаю, в якій версії Ruby / Rails це працювало, але для ruby ​​2.5 і rails 5, здається, не працює.
Tasos Anesiadis

13

Поставте знак мінус перед ім'ям_столбця та змініть напрямок порядку. Це працює на mysql. Детальніше

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last

10

Трохи запізнився на шоу, але існує загальний спосіб SQL це зробити. Як завжди, CASEна допомогу.

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')


6

Для нащадків я хотів виділити помилку ActiveRecord, що стосується NULLS FIRST .

Якщо ви намагаєтесь зателефонувати:

Model.scope_with_nulls_first.last

Rails намагатиметься викликати reverse_order.firstта reverse_orderне сумісний із NULLS LAST, оскільки намагається сформувати недійсний SQL:

PG::SyntaxError: ERROR:  syntax error at or near "DESC"
LINE 1: ...dents"  ORDER BY table_column DESC NULLS LAST DESC LIMIT...

Про це згадувалося кілька років тому в деяких досі відкритих випусках Rails ( один , два , три ). Я зміг обійти це, виконавши наступне:

  scope :nulls_first, -> { order("table_column IS NOT NULL") }
  scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }

Здається, об’єднуючи два порядки разом, генерується дійсний SQL:

Model Load (12.0ms)  SELECT  "models".* FROM "models"  ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1

Єдиним недоліком є ​​те, що цей ланцюжок повинен бути зроблений для кожного обсягу.


2

У моєму випадку мені потрібно було сортувати рядки за датою початку та закінчення за ASC, але в деяких випадках end_date був нульовим, і ці рядки мали бути вгорі, я використовував

@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')


-3

Здається, вам потрібно було б це зробити в Ruby, якщо ви хочете мати послідовні результати між типами баз даних, оскільки сама база даних інтерпретує, чи мають значення NULLS перед чи в кінці списку.

Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}

Але це не дуже ефективно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.