Ruby On Rails PR

Railsでデータの件数を取得する方法を解説!count, size, lengthの違いは?

Railsでデータの件数を取得する方法を解説!count-size-lengthの違いは?
記事内に商品プロモーションを含む場合があります

Railsを使用していると、データベースからのレコード件数の取得方法に迷うことがよくあります。特に、count, size, lengthという3つのメソッドが存在するため、どれを選択すれば最適なのか疑問に思うことがあると思います。

この記事では、そんな疑問を解消するために、以下の内容を詳しく解説します。

本記事の内容
  1. count, size, lengthのそれぞれの特性と使用シーン
  2. 各メソッドの内部での動作の違い
  3. 最適なシチュエーションでのメソッドの選択方法

Active RecordはRailsの強力な機能の一つで、データベース操作を直感的に行うことができます。しかし、その中でも件数取得のメソッドは微妙な違いがあり、その選択はパフォーマンスや効率に大きく影響します。

この記事を通して3つのメソッドの違いを正確に理解し、日々の開発での最適な選択ができるようになりましょう。

countは必ずクエリを発行する

今回の記事ではRails Consoleを活用して、Usersテーブルのレコードを全件取得した後に各メソッドを実行したらどの様に処理されるかを見ていきます。

Rails Consoleはプロジェクトルートでbin/rails cを実行する事で利用が可能です。

まずはcountメソッドの挙動です。

irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得

irb(main):002:0> users.count #取得したレコードの件数を取得
   (0.6ms)  SELECT COUNT(*) FROM `users`
=> 3

irb(main):003:0> users.count #取得したレコードの件数を再度取得
   (1.9ms)  SELECT COUNT(*) FROM `users`
=> 3

ここで注目して欲しいのはcountメソッドを実行すると毎回データベースに対してクエリを投げているという点です。

SELECT COUNT(*) FROM `users`

eachなどのループ処理の中でcountを実行する際にな十分注意しましょう。

  • countは実行時に必ずデータベースに対してクエリを発行する
  • 毎回クエリを発行するため必ず最新のレコードの件数を取得出来る

 

lengthはメモリに情報をロード(キャッシュ)する

irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得

irb(main):002:0> users.length #取得したレコードの件数を取得
  User Load (0.3ms)  SELECT `users`.* FROM `users`
=> 3

irb(main):003:0> users.length #取得したレコードの件数を再度取得
=> 3

lengthは1つめのcountと違い実行時にメモリに情報を保存します。

そのため、2回目以降のlengthではSELECT users.* FROM usersが実行(表示)されていない点に注目しましょう。

  • 初回のlength実行時にデータをメモリに保存している
  • 一度データをロードした後は何度lengthを実行しても新たなクエリは発行されない

 

sizeはlengthとcountのあわせ技

irb(main):001:0> users = User.all #usersテーブルのレコードを全件取得

irb(main):002:0> users.size #取得したレコードの件数を取得
   (0.6ms)  SELECT COUNT(*) FROM `users`
=> 3

irb(main):003:0> users.size #取得したレコードの件数を再度取得
   (0.7ms)  SELECT COUNT(*) FROM `users`
=> 3

sizeはデータをallで取得した後に実行するとcountと同様に必ずクエリを発行します。

しかし下記の様に間にlengthを挟みメモリ上にデータを乗せておくと、sizelengthと同様にメモリから取得するようになりクエリを発行しなくなります。

irb(main):001:0> users = User.all

irb(main):002:0> users.length
  User Load (0.3ms)  SELECT `users`.* FROM `users`
=> 3

irb(main):003:0> users.size
=> 3
  • メモリにデータが無い場合はcountと同じくクエリを発行する
  • メモリにデータがある場合はlengthと同じくクエリを発行せずに件数を取得可能

 

loadメソッドでメモリにデータを格納する

lengthを使用するとデータはメモリ上にキャッシュされ、クエリの再発行が不要になります。しかし、lengthを用いてデータをメモリにキャッシュした後にcountを使用するのは少し違和感があるかと思います。

このような状況では、loadメソッドを使用してデータをメモリ上にロードし、その後sizeを使うという方法があります。

irb(main):001:0> users = User.all

irb(main):002:0> users.load
  User Load (0.3ms)  SELECT `users`.* FROM `users`
=> #, #, #]>

irb(main):003:0> users.size
=> 3

irb(main):004:0> users.size
=> 3

loadを使った後はlengthの時と同様に、sizeで件数を取得してもクエリが発行されないようになります。

件数を取得するだけならメソッドチェーン

irb(main):001:0> count = User.all.count
   (0.7ms)  SELECT COUNT(*) FROM `users`
=> 3

今までのコードでは一度usersという変数にallの結果を入れてからcountなどの件数を取得するメソッドを実行していました。

しかしレコードの各カラム情報が不要で件数だけが必要な場合は、メソッドチェーンを使うことでよりシンプルに書けます。

件数を取得する場合はどのメソッドを使うべきか

基本的にはlengthcountの合せ技のsizeを使うのが安定ですが、1つだけ注意点があります。

それはデータがメモリ上に乗っている(キャッシュされている)場合は最新の件数を取得出来ない点です。

irb(main):001:0> users = User.all.load #usersテーブルのレコードを全件取得しキャッシュする
  User Load (0.3ms)  SELECT `users`.* FROM `users`

irb(main):002:0> users.size #キャッシュの情報を元に件数を取得するのでクエリは発行されない
=> 3

irb(main):003:0> User.create(id: 4) #1件ユーザーのレコードを追加する
   (0.2ms)  BEGIN

  User Create (0.3ms)  INSERT INTO `users` (`id`, `created_at`, `updated_at`) VALUES (4, '2021-01-16 14:56:19.564822', '2021-01-16 14:56:19.564822')
   (1.1ms)  COMMIT
=> #

irb(main):004:0> users.size #usersの情報はキャッシュされているので件数は3件のまま
=> 3

irb(main):005:0> users.count #実際は1件レコードを作成したので4件存在する
   (0.6ms)  SELECT COUNT(*) FROM `users`
=> 4

countは必ずクエリを発行するため、処理の途中でデータがインサート(作成)された場合でも常に最新の件数を取得することが出来ます。

一方sizeはデータがキャッシュされている場合は、クエリを発行せずにキャッシュから件数を取得するために必ずしも正確な件数を取得出来ない場合があります。

何も考えずにsizeメソッドを使った場合にはこの様な罠があるという事を覚えておきましょう。

メソッドは理解した上で使うべき

似たような処理を行うメソッドでも内部の処理は大きく異なります。

具体的にどのような処理が行われているかを意識しその時の最適なメソッドを選択する事で、パフォーマンスを意識したより良い実装を出来るようになりましょう。

 

ABOUT ME
himakuro
新卒で入社したブラック企業から脱出して超ホワイトな会社に転職。エンジニア歴は7年で普段はウェブサービス作ったりブログを書いたり、MENTAで未経験者の方にプログラミングを指導しています。
PR
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール