💭

【Rails】あるカラムがNULLのレコードを優先的に取得したい場合の書き方

に公開

たとえば、Todoというモデルがあり、Todo#done_atという属性があるとする(datetime型)。
Todo#done_atにデータが存在する場合は完了の判定、nilの場合は未完了とする(この記事では設計についての議論はしない)。

実現したい要件:

  • 未完了のTodoデータを先に表示する
  • 完了済みのTodoデータをその次に表示する
    • 完了済みのTodoデータは降順に表示する

クエリはこんな感じになる。

Todo.order(Arel.sql('done_at IS NULL DESC'), done_at: :desc)

もしくは、Arel のnulls_firstメソッドを使う書き方もある。

Todo.order(Todo.arel_table[:done_at].desc.nulls_first)

descdone_atを降順にソートし、nulls_firstで NULL 値を先頭に配置する。

'done_at IS NULL DESC'は「done_at が NULL のレコードを先に持ってきて、NULL でないレコードを後ろに配置する」という意味になる。つまり、未完了のレコードが先にきて、完了済みのレコードは後ろに来る。

ここにDESCがある理由は、done_at IS NULLが TRUE(つまりカラムにデータがなく、未完了)の場合、DESC (降順)を指定することで、TRUE の値が FALSE の値よりも「大きい」と解釈される。よって、未完了のデータが先に来る。

Arelを使わない場合はこういう書き方になるが、アラートが出るので注意。

Todo.order('done_at IS NULL DESC, done_at DESC')

吐き出されるSQLはこんな感じになる。

ORDER BY done_at IS NULL DESC, done_at DESC

可読性を考えるなら、次のような書き方もある。

Todo
  .order(Arel.sql('done_at IS NULL DESC'))
  .order(done_at: :desc)

Discussion