1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsのfind_eachやfind_in_batchesでorderにid以外を指定したい場合の解決方法

Last updated at Posted at 2021-02-25

はじめに

大量のデータにアクセスして処理を行う場合、

  • メモリ不足で処理が中断されないよう、少しずつメモリに展開したい
  • 途中で処理が中断されても問題ないよう、一定件数ごとにコミットをしたい

と考えることがあると思います。

そんなときにRailsで役に立つのがfind_eachやfind_in_batchesですね。

ただしこの2つのメソッドには弱点があり、id(主キー/primary key)の昇順(ASC)でしかデータを扱うことができません

※Rails v6.1.0時点での情報です。

    # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
    # ascending on the primary key ("id ASC").
    # This also means that this method only works when the primary key is
    # orderable (e.g. an integer or string).

そこで、今回は自分が指定したorderで大量データを扱いたい場合の解決方法を紹介いたします。

解決方法

前提

Rankingモデルのrank(順位)順で処理をしたい、とします。

実装

@rank_offset = 0
@batch_size = 1000

def find_rankings_in_batches
  loop do
    rankings = Ranking.where('rank > ?', @rank_offset).order(rank: :asc).limit(@batch_size)
    break if rankings.blank?
    rankings.each do |ranking|
      yield(ranking)
    end
    @rank_offset = rankings.last.rank
  end
end

find_rankings_in_batches do |ranking|
  # ここに処理を書く
end

発行されたSQL

1ループ目

SELECT  `rankings`.* FROM `rankings` WHERE (rank > 0)  ORDER BY `rankings`.`rank` ASC LIMIT 1000

2ループ目

SELECT  `rankings`.* FROM `rankings` WHERE (rank > 1000)  ORDER BY `rankings`.`rank` ASC LIMIT 1000

rankの昇順かつbatch_size単位で取得できていることがわかります。

注意点

基本的には、orderに指定するカラムはUNIQUE制約が設定されているものにしてください。
batchの切れ目で同じ値が続く場合、処理されないレコードが存在してしまうためです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?