emolog

脳内メモです。

ActiveRecordでレコードの合計値を取得する場合は、countよりsize使おうという話

f:id:ababababa0222:20200430025006p:plain

結論(仮)

  • countよりsize使ったほうが便利

sizeとcountの挙動の違いについて

size

  • メモリに乗っているときはクエリ発行せずメモリ上から計算
  • なければクエリ発行

以下railsapiドキュメントからの引用

size()Link Returns the size of the collection. If the collection hasn't been loaded, it executes a SELECT COUNT(*) query. Else it calls collection.size. If the collection has been already loaded size and length are equivalent. If not and you are going to need the records anyway length will take one less query. Otherwise size is more efficient.

# Person: 1 に対して pets: N
class Person < ActiveRecord::Base
  has_many :pets
end

# 一度クエリ発行
person.pets # This will execute a SELECT * FROM query
# => [
#       #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
#       #<Pet id: 2, name: "Spook", person_id: 1>,
#       #<Pet id: 3, name: "Choo-Choo", person_id: 1>
#    ]
# 再度クエリは発行されずcountが返却される / loaded状態
# ActiveRecord Association がメモリに乗っている形
person.pets.size # => 3
# Because the collection is already loaded, this will behave like
# collection.size and no SQL count query is executed.

api.rubyonrails.org

count

  • loaded状態でもかならずSQLを発行する

つまりどういうことか

# ここでキャッシュしたい
pry(main)> user = User.includes(:article_comments).find(1)
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  ArticleComment Load (0.3ms)  SELECT `article_comments`.* FROM `article_comments` WHERE `article_comments`.`user_id` = 1
# キャッシュしたいのに再度クエリが発行されてしまっている
pry(main)> user.article_comments.count
   (0.5ms)  SELECT COUNT(*) FROM `article_comments` WHERE `article_comments`.`user_id` = 1
=> 1
  • なのでincludes等でN + 1クエリを防ごうとしても、都度SQLが実行されてしまう。

参考

メモ

  • size / count / lengthの実装を確認