Ruby, Railsの話です。
ちょっとした集計処理には色々な書き方があるなぁと思っていて、今携わっているプロジェクトにもいろんな書き方があったので比べてみるコトにしました。
検証環境
ruby 2.5.1
rails 5.2.1
postgresql 10.5
問題状況
例えば注文明細の情報があり、「単価(price)」と「数量(quantity)」のデータを持っているとしましょう。
このデータを使って注文金額の合計値を計算したい場合について考えてみます。
orders テーブルのスキーマ:
create_table "orders", force: :cascade do |t| t.integer "price" t.integer "quantity" t.datetime "created_at", null: false t.datetime "updated_at", null: false end
計算ロジック
注文金額の合計値は全レコード分の price * quantity
を足し合わせた値なので以下のような方法で計算することができそうです。
Enumerable#inject を使う場合
Order.all.inject(0){|sum, order| sum + order.price * order.quantity}
instance method Enumerable#inject (Ruby 2.6.0)
Array#sum を使う場合
Order.all.map{|order| order.price * order.quantity}.sum
instance method Array#sum (Ruby 2.6.0)
ActiveRecord#sum を使う場合
Order.all.sum("price*quantity")
https://devdocs.io/rails~5.2/activerecord/calculations#method-i-sum
比較してみた
毎回データを取得する場合
number_of_trial = 500 Order.uncached do Benchmark.bm 20 do |r| r.report "Enumerable#inject" do number_of_trial.times do Order.all.inject(0){|sum, order| sum + order.price * order.quantity} end end r.report "Array#sum" do number_of_trial.times do Order.all.map{|order| order.price * order.quantity}.sum end end r.report "ActiveRecord#sum" do number_of_trial.times do Order.all.sum("price*quantity") end end end end
user system total real Enumerable#inject 6.380000 0.140000 6.520000 ( 8.091545) Array#sum 7.150000 0.040000 7.190000 ( 8.771430) ActiveRecord#sum 0.260000 0.070000 0.330000 ( 1.081005)
前もって取得しておいたデータを使う場合
number_of_trial = 500 Order.uncached do Benchmark.bm 20 do |r| orders = Order.all r.report "Enumerable#inject" do number_of_trial.times do orders.inject(0){|sum, order| sum + order.price * order.quantity} end end r.report "Array#sum" do number_of_trial.times do orders.map{|order| order.price * order.quantity}.sum end end r.report "ActiveRecord#sum" do number_of_trial.times do orders.sum("price*quantity") end end end end
user system total real Enumerable#inject 0.370000 0.000000 0.370000 ( 0.408662) Array#sum 0.350000 0.000000 0.350000 ( 0.356449) ActiveRecord#sum 0.260000 0.030000 0.290000 ( 1.094346)
比較結果
Enumerable#inject
を使うかArray#sum
を使うかは、好みの話かも。- 該当コードの文脈に合わせてわかりやすい方を使えば良さそう
- 合計値だけが欲しい場合は
ActiveRecord#sum
を使うと良さそう。 - 個別のデータは他でよしなに使いつつ、ついでに合計値も出したい、というケースでは
Enumerable#inject
なりArray#sum
を使った方が良さそう。
まとめ
今回はちょっとした集計ロジックを比較してみました。
データの取得が絡んでいるのでキャッシュ使わないよう uncached
したり、処理時間を比較するために Benchmark
を使ってみています。(詳しく調べたらまた記事にしようかな〜)
ではまた。