Рельсы: заказ с нулями последний
В моем приложении Rails я столкнулся с проблемой пару раз, что я хотел бы знать, как другие люди решают:
у меня есть определенные записи, где значение является необязательным, поэтому некоторые записи имеют значение, а некоторые-null для этого столбца.
Если я заказываю по этому столбцу на некоторых базах данных нули сортируются первыми, а на некоторых базах данных нули сортируются последними.
например, у меня есть фотографии, которые могут или не могут принадлежать к коллекции, т. е. есть несколько фотографий, где collection_id=nil а кое где collection_id=1 etc.
Если я это сделаю Photo.order('collection_id desc) затем на SQLite я получаю нули в последнюю очередь, но на PostgreSQL я получаю нули в первую очередь.
есть ли хороший, стандартный способ Rails справиться с этим и получить согласованную производительность в любой базе данных?
11 ответов:
добавление массивов вместе сохранит порядок:
@nonull = Photo.where("collection_id is not null").order("collection_id desc") @yesnull = Photo.where("collection_id is null") @wanted = @nonull+@yesnull
Я не эксперт в SQL, но почему бы просто не отсортировать, если что-то сначала null, а затем сортировать по тому, как вы хотели его отсортировать.
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но SQLite3 даст вам ошибки.
несмотря на то, что сейчас 2017 год, до сих пор нет консенсуса по поводу того,
NULLs должно иметь приоритет. Без вашего явного об этом, ваши результаты будут варьироваться в зависимости от СУБД.стандарт не указывает, как null должны быть упорядочены по сравнению с ненулевыми значениями, за исключением того, что любые два null должны считаться одинаково упорядоченными, и что null должны сортировать либо выше, либо ниже всех ненулевых значений.
чтобы проиллюстрировать проблему, я составил список из нескольких наиболее популярных случаев, когда речь заходит о разработке Rails:
PostgreSQL
NULLs имеют самое высокое значение.по умолчанию значения null сортируются так, как будто они больше любого ненулевого значения.
MySQL
NULLs имеют самое низкое значение.при выполнении ORDER BY сначала отображаются нулевые значения, если вы выполняете ORDER BY ... ASC и последний, если вы делаете заказ ... ОПИСАНИЕ.
SQLite
NULLs имеют самое низкое значение.строка с нулевым значением выше, чем строки с регулярные значения в порядке возрастания, и он отменяется для нисходящего порядка.
решение
к сожалению, Rails сам по себе еще не дает решения для этого.
PostgreSQL specific
для PostgreSQL вы можете вполне интуитивно использовать:
Photo.order('collection_id DESC NULLS LAST') # NULLs come lastMySQL specific
для MySQL вы можете поставить знак минус заранее, но это можно без документов. Похоже, что он работает не только с числовыми значениями, но и с датами.
Photo.order('-collection_id DESC') # NULLs come lastPostgreSQL и MySQL specific
чтобы прикрыть их обоих, это, кажется, работает:
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.
Put минус перед column_name и изменить направление заказа. Он работает на MySQL. более подробная информация
Product.order('something_date ASC') # NULLS came first Product.order('-something_date DESC') # NULLS came last
Photo.order('collection_id DESC NULLS LAST')Я знаю, что это старый, но я только что нашел этот фрагмент, и он работает для меня.
немного поздно для шоу, но есть общий способ SQL, чтобы сделать это. Как обычно,
CASEк спасению.Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
ради потомства, я хотел бы выделить ошибку ActiveRecord, связанную с
NULLS FIRST.если вы попытаетесь назвать:
Model.scope_with_nulls_first.lastRails попытается вызвать
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единственным недостатком является то, что эта цепочка должна быть сделана для каждого объем.
в моем случае мне нужно было сортировать строки по дате начала и окончания по ASC, но в некоторых случаях end_date был равен нулю, и эти строки должны быть выше, я использовал
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
похоже, вам придется сделать это в Ruby, если вы хотите получить согласованные результаты по типам баз данных, так как сама база данных интерпретирует, идут ли нули в начале или в конце списка.
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}но это не очень эффективно.
сначала получить фотографии, где
collection_idне null и приказаcollection_idпо убыванию.тогда получить фотографии, где
collection_idравно null и добавьте их.Photos.where.not(collection_id: [nil, ""]) .order(collection_id: :desc) + where(collection_id: [nil, ""])
Comments