Rails: порядок с нулями

В моем приложении Rails я столкнулся с проблемой пару раз, чтобы узнать, как другие люди решают:

У меня есть определенные записи, где значение является необязательным, поэтому некоторые записи имеют значение, а некоторые - для этого столбца.

Если я заказываю по этому столбцу в некоторых базах данных сначала сортировку нулей, а в некоторых базах - сортировку нулей.

Например, у меня есть Фотографии, которые могут или не могут принадлежать коллекции, т.е. есть некоторые фотографии, где collection_id=nil и некоторые, где collection_id=1 и т.д.

Если я делаю Photo.order('collection_id desc), то на SQLite я получаю нулевые значения, но на PostgreSQL я сначала получаю нули.

Есть ли хороший стандартный Rails способ справиться с этим и получить согласованную производительность в любой базе данных?

Ответ 2

Я не эксперт по SQL, но почему бы просто не отсортировать, если что-то сначала пустое, а затем сортировать по его сортировке.

Photo.order('collection_id IS NULL, collection_id DESC')  # Null last
Photo.order('collection_id IS NOT NULL, collection_id DESC') # Null first

Если вы используете PostgreSQL, вы также можете сделать это

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

Но SQLite3 даст вам ошибки.

Ответ 3

Поместите знак минус перед именем column_name и измените направление заказа. Он работает на mysql. Подробнее

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

Ответ 4

Photo.order('collection_id DESC NULLS LAST')

Я знаю, что это старый, но я нашел этот фрагмент, и он работает для меня.

Ответ 5

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

В стандарте не указывается, как NULL следует заказывать по сравнению с значениями, отличными от NULL, за исключением того, что любые два NULL должны считаться одинаково упорядоченными и что NULL должны сортировать либо выше, либо ниже всех значений, отличных от NULL.

источник, сравнение большинства СУБД

Чтобы проиллюстрировать проблему, я собрал список нескольких наиболее популярных случаев, когда речь заходит о разработке Rails:

PostgreSQL

NULL имеют наибольшее значение.

По умолчанию нулевые значения сортируются как большие, чем любое ненулевое значение.

источник: документация PostgreSQL

MySQL

NULL имеют самое низкое значение.

При выполнении ORDER BY значения NULL отображаются сначала, если вы выполняете ORDER BY... ASC и последний, если вы выполняете ORDER BY... DESC.

источник: документация по MySQL

SQLite

NULL имеют самое низкое значение.

Строка со значением 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.

Ответ 6

Бит опоздал на показ, но есть общий способ SQL для этого. Как обычно, CASE для спасения.

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

Ответ 7

Самый простой способ - использовать:

.order('name nulls first')

Ответ 8

Для потомков я хотел выделить ошибку 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

Единственным недостатком является то, что эта цепочка должна выполняться для каждой области.

Ответ 9

В моем случае мне нужны строки сортировки по дате начала и окончания ASC, но в некоторых случаях end_date был пустым и что строки должны быть выше, я использовал

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

Ответ 10

Похоже, вам нужно будет сделать это в Ruby, если вы хотите получить согласованные результаты по типам баз данных, поскольку сама база данных интерпретирует, идут ли NULLS в начале или конце списка.

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

Но это не очень эффективно.