Rails: последний заказ с нулями - PullRequest
72 голосов
/ 29 апреля 2011

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

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

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

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

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

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

Ответы [ 11 ]

234 голосов
/ 14 августа 2011

Я не эксперт по 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')
31 голосов
/ 04 июля 2017

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

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

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

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

PostgreSQL

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

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

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

MySQL

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

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

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

SQLite

NULL s имеют наименьшее значение.

Строка сЗначение NULL ha больше, чем строки с обычными значениями в порядке возрастания, и оно инвертировано для порядка убывания.

source

Solution

К сожалению, сам 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.

12 голосов
/ 26 июля 2016
Photo.order('collection_id DESC NULLS LAST')

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

12 голосов
/ 30 января 2013

Поместите знак минуса перед столбцом имя-столбца и измените направление ордера.Это работает на MySQL. Подробнее

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
7 голосов
/ 28 августа 2015

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

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
6 голосов
/ 10 октября 2015

Ради потомков, я хотел бы выделить ошибку ActiveRecord, касающуюся NULLS FIRST.

Если вы попытаетесь вызвать:

Model.scope_with_nulls_first.last

Rails попытается вызвать reverse_order.firstreverse_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 ( one , два , три ).Я смог обойти это, выполнив следующие действия:

  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

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

5 голосов
/ 23 февраля 2012

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

.order('name nulls first')

2 голосов
/ 29 апреля 2011

Добавление массивов вместе сохранит порядок:

@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

1 голос
/ 02 февраля 2017

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

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

0 голосов
/ 06 апреля 2018

Сначала получите фотографии, где collection_id не равно нулю и упорядочены по collection_id по убыванию.

Затем получите фотографии, где collection_id равно нулю, и добавьте их.

Photos.where.not(collection_id: [nil, ""])
  .order(collection_id: :desc) + where(collection_id: [nil, ""])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...