🐷
SQLの集計にはLIMITが効かないらしい
いくら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