🐷

SQLの集計にはLIMITが効かないらしい

2022/12/14に公開約1,500字

いくらLIMITしても平均値が変わらないので調べたらLIMIT効いてなかった。

require "active_record"
ActiveRecord::VERSION::STRING   # => "7.0.4"
ActiveSupport::LogSubscriber.colorize_logging = false
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
  create_table :items do |t|
    t.integer :score
  end
end

class Item < ActiveRecord::Base
end

Item.create!(score: 1)    # => #<Item id: 1, score: 1>
Item.create!(score: 2)    # => #<Item id: 2, score: 2>
Item.create!(score: 100)  # => #<Item id: 3, score: 100>

Item.order(:id).limit(2).average(:score).to_f  # => 34.33333333333333
# >>   Item Average (0.1ms)  SELECT AVG("items"."score") FROM "items" LIMIT ?  [["LIMIT", 2]]

対策1. 長期的になら副SQLにする

Item.where(id: Item.order(:id).limit(2)).average(:score).to_f  # => 1.5
# >>   Item Average (0.1ms)  SELECT AVG("items"."score") FROM "items" WHERE "items"."id" IN (SELECT "items"."id" FROM "items" ORDER BY "items"."id" ASC LIMIT ?)  [["LIMIT", 2]]

それかすでに [1, 2] を取得済みなら where(id: [1, 2]) となるように渡す

ちなみに id を明示したからといって LIMIT が効いたりはしない

Item.where(id: [1, 2]).limit(1).average(:score).to_f      # => 1.5

というか無意味な LIMIT の指定があればエラーを出してくれ(小声)

対策2. 今だけでいいならRDBを使わなくてもいい

scores = Item.order(:id).limit(2).pluck(:score)
# >>   Item Pluck (0.1ms)  SELECT "items"."score" FROM "items" ORDER BY "items"."id" ASC LIMIT ?  [["LIMIT", 2]]
scores.sum.fdiv(scores.size)      # => 1.5

Discussion

ログインするとコメントできます