PostgreSQLではINTEGER型のカラムをorderでソートするときNULLが先頭に来てしまう

またしてもデータベースの違いでつまづきました。
とりあえずこれですべて片付いたとは思うけど、今度から開発環境と本番環境で同じDBを使おうと心に誓いました……。


現象

記事(article)にいいね数(likes_count)カラムがあり、その数値でソートして人気記事ランキングを作ります。

article.rb

  #like数が多い記事TOP10
  def self.popular_10
    includes(:blog).order(:likes_count).reverse[0..9]
  end

開発環境のSQLiteではきちんといいね数が多い順に取得できていたのですが、
HerokuにデプロイしてPostgreSQLになったらなぜかいいね数ゼロの記事を取得してしまう。

原因

PostgreSQLではinteger型のカラムをorderでソートしたとき、デフォルトでnull値が先頭に来てしまうようです。

いいね数がnullのデータが3つと 1, 10, 30のデータがあるとして、これをDESCで並び替えると

null
null
null
30
10
1

というように並びます。
今回の場合はいいね数0の記事が10個以上あるので、nullの記事に邪魔されて取得できなかったのですね。


解決法

  #like数が多い10記事からランダムに取得
  def self.popular_10
    includes(:blog).order('likes_count IS NULL, likes_count DESC')[0..9]
  end

このようにすればNULLの値が最後になり、意図した通りの動きをします。
IS NOT NULLにするとNULLの値が最初になります。何も記述しない場合と同じ動きですね。

ちなみにPostgreSQLだと

order('likes_count DESC NULLS LAST')

という記述も可能だそうです。こっちのほうがスマートですね。
ただSQLiteでは使えませんので注意。

参考リンク

Rails: Order with nulls last - Stack Overflow

WEB開発備忘録 PostgreSQLでorderする際の、null値の扱い