В моем приложении Rails я пару раз сталкивался с проблемой, которую мне хотелось бы знать, как ее решают другие:
У меня есть определенные записи, в которых значение не является обязательным, поэтому некоторые записи имеют значение, а некоторые - null для этого столбца.
Если я упорядочу по этому столбцу в некоторых базах данных, сначала сортируются нули, а в некоторых базах данных - последним.
Например, у меня есть фотографии, которые могут принадлежать или не принадлежать к Коллекции, т.е. есть фотографии где- collection_id=nil
то, где-то collection_id=1
и т.
Если я это сделаю, Photo.order('collection_id desc)
то в SQLite я получаю нули последними, но в PostgreSQL я сначала получаю нули.
Есть ли хороший стандартный способ Rails справиться с этим и добиться стабильной производительности в любой базе данных?
where
он не возвращает массив, он возвращает ActiveRecord :: Relation, а принудительное занесение результатов в массив приведет к сбою всего, что ожидает стандартного ActiveRecord :: Relation (например, разбиения на страницы).Я не эксперт в 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
Если вам нужно что-то универсальное (например, вы используете один и тот же запрос в нескольких базах данных, вы можете использовать (любезно предоставлено @philT)
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
источник
p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
NULLS LAST
это не позволяет вам подключаться.last()
к вашему запросу. Я опубликовал обходной путь ниже.IS NULL
строки NULL помещаются в последнюю очередь? Можно подумать, это поставит их на первое место.Несмотря на то, что сейчас 2017 год, до сих пор нет единого мнения о том,
NULL
должны ли s иметь приоритет. Если вы не говорите об этом явно, ваши результаты будут зависеть от СУБД.Чтобы проиллюстрировать проблему, я составил список из нескольких наиболее популярных случаев, когда дело доходит до разработки на Rails:
PostgreSQL
NULL
s имеют наибольшее значение.MySQL
NULL
s имеют наименьшее значение.SQLite
NULL
s имеют наименьшее значение.Решение
К сожалению, сам 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
.источник
Photo.order('collection_id DESC NULLS LAST')
Я знаю, что это старый, но я только что нашел этот фрагмент, и он мне подходит.
источник
Поместите знак минус перед column_name и измените направление порядка. Работает на mysql. Подробнее
Product.order('something_date ASC') # NULLS came first Product.order('-something_date DESC') # NULLS came last
источник
Немного поздно на шоу, но есть общий способ SQL. Как обычно,
CASE
на помощь.Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
источник
Самый простой способ - использовать:
.order('name nulls first')
источник
last
кажется, вставляет ошибочный DESC.Ради потомков я хотел выделить ошибку ActiveRecord, связанную с
NULLS FIRST
.Если вы попытаетесь позвонить:
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
Единственным недостатком является то, что эту цепочку нужно выполнять для каждой области.
источник
В моем случае мне нужны были строки сортировки по дате начала и окончания по ASC, но в некоторых случаях end_date был нулевым, и эти строки должны быть выше, я использовал
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
источник
Похоже, вам придется сделать это в Ruby, если вы хотите получить согласованные результаты для разных типов баз данных, поскольку сама база данных интерпретирует, идут ли NULL в начале или в конце списка.
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}
Но это не очень эффективно.
источник