💭
【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)
desc
でdone_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