私のRailsアプリでは、他の人がどのように解決するかを知りたいという問題に何度か遭遇しました:
値がオプションである特定のレコードがあるため、一部のレコードには値があり、一部のレコードにはその列がヌルです。
一部のデータベースでその列を並べ替えると、NULLが最初にソートされ、一部のデータベースではNULLが最後にソートされます。
たとえば、コレクションに属しているかどうかに関係なく写真があります。つまり、_collection_id=nil
_の写真と__collection_id=1
_などの写真があります。
Photo.order('collection_id desc)
を実行すると、SQLiteでは最後にNULLが取得されますが、PostgreSQLでは最初にNULLが取得されます。
これを処理し、すべてのデータベースで一貫したパフォーマンスを得るための素敵で標準的なRailsの方法はありますか?
配列を一緒に追加すると、順序が維持されます。
@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
普遍的なものが必要な場合(複数のデータベースで同じクエリを使用している場合など)を使用できます(@philT提供)
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
2017年になりましたが、NULL
sを優先すべきかどうかについてはまだ意見が一致していません。それを明示しないと、結果はDBMSによって異なります。
標準では、非NULL値と比較したNULLの順序付け方法は指定されていません。ただし、2つのNULLは等しく順序付けられていると見なされ、NULLはすべての非NULL値の上または下にソートされます。
この問題を説明するために、Rails開発に関する最も一般的ないくつかのケースのリストを作成しました。
NULL
sの値が最大です。
デフォルトでは、null値はnull以外の値よりも大きいかのようにソートされます。
NULL
sの値は最低です。
ORDER BYを実行すると、ORDER BY ... ASCを実行するとNULL値が最初に表示され、ORDER BY ... DESCを実行すると最後にNULL値が表示されます。
NULL
sの値は最低です。
NULL値を持つ行は、昇順の通常値を持つ行よりも高く、降順では逆になります。
残念ながら、Rails自体はまだ解決策を提供していません。
PostgreSQLの場合、非常に直感的に使用できます。
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
MySQLの場合、マイナス記号を前もって置くことができますが、この機能は文書化されていないようです。数値だけでなく、日付でも機能するように見えます。
Photo.order('-collection_id DESC') # NULLs come last
両方をカバーするために、これはうまくいくようです:
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
で並べ替えます。
マイナス記号を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')
後世のために、NULLS FIRST
に関連するActiveRecordエラーを強調したかった。
あなたが電話しようとする場合:
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 issue( one 、 two 、 three )。次の操作を行うことで回避できました。
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スコープに対して行われなければならないことです。
最も簡単な方法は以下を使用することです:
.order('name nulls first')
私の場合、ASCで開始日と終了日で行を並べ替える必要がありましたが、end_dateがnullで、その行が上にある必要がある場合はほとんどありませんでした。
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')