web-dev-qa-db-ja.com

Rails:最後にnullのある注文

私のRailsアプリでは、他の人がどのように解決するかを知りたいという問題に何度か遭遇しました:

値がオプションである特定のレコードがあるため、一部のレコードには値があり、一部のレコードにはその列がヌルです。

一部のデータベースでその列を並べ替えると、NULLが最初にソートされ、一部のデータベースではNULLが最後にソートされます。

たとえば、コレクションに属しているかどうかに関係なく写真があります。つまり、_collection_id=nil_の写真と__collection_id=1_などの写真があります。

Photo.order('collection_id desc)を実行すると、SQLiteでは最後にNULLが取得されますが、PostgreSQLでは最初にNULLが取得されます。

これを処理し、すべてのデータベースで一貫したパフォーマンスを得るための素敵で標準的なRailsの方法はありますか?

75
Andrew

配列を一緒に追加すると、順序が維持されます。

@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
Eric

私は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')
238
Intentss

2017年になりましたが、NULLsを優先すべきかどうかについてはまだ意見が一致していません。それを明示しないと、結果はDBMSによって異なります。

標準では、非NULL値と比較したNULLの順序付け方法は指定されていません。ただし、2つのNULLは等しく順序付けられていると見なされ、NULLはすべての非NULL値の上または下にソートされます。

ソース、ほとんどのDBMSの比較

この問題を説明するために、Rails開発に関する最も一般的ないくつかのケースのリストを作成しました。

PostgreSQL

NULLsの値が最大です。

デフォルトでは、null値はnull以外の値よりも大きいかのようにソートされます。

ソース:PostgreSQLドキュメント

MySQL

NULLsの値は最低です。

ORDER BYを実行すると、ORDER BY ... ASCを実行するとNULL値が最初に表示され、ORDER BY ... DESCを実行すると最後にNULL値が表示されます。

ソース: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では機能しません

ユニバーサルソリューション

すべてのDBMSのクロスサポートを提供するには、すでに@PhilITによって提案されているCASEを使用してクエリを記述する必要があります。

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

これは、最初に各レコードを最初にCASEの結果(デフォルトでは昇順、つまりNULLの値が最後の値になる)で並べ替え、2番目にcalculation_idで並べ替えます。

31
Adam Sibik

マイナス記号をcolumn_nameの前に置き、順序を逆にします。 mysqlで動作します。 詳細

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
12
raymondralibi
Photo.order('collection_id DESC NULLS LAST')

私はこれが古いものであることを知っていますが、このスニペットを見つけたばかりで、私にとってはうまくいきます。

12
Thomas Yancey

ショーに少し遅れましたが、それを行う一般的なSQLの方法があります。いつものように、CASEが助けになります。

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

後世のために、NULLS FIRSTに関連するActiveRecordエラーを強調したかった。

あなたが電話しようとする場合:

Model.scope_with_nulls_first.last

Railsはreverse_order.firstの呼び出しを試みますが、reverse_orderNULLS LASTと互換性がありません。無効なSQLを生成しようとするためです。

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

これは数年前にいくつかのまだオープンな状態で参照されていましたRails issue( onetwothree )。次の操作を行うことで回避できました。

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

2つの注文を連結すると、有効なSQLが生成されるようです。

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

唯一の欠点は、この連鎖がeachスコープに対して行われなければならないことです。

6
Lanny Bose

最も簡単な方法は以下を使用することです:

.order('name nulls first')

5

私の場合、ASCで開始日と終了日で行を並べ替える必要がありましたが、end_dateがnullで、その行が上にある必要がある場合はほとんどありませんでした。

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

1
Dmitriy Gusev