はじめに
Ruby on Railsを使ってAPIを開発していたときに遭遇したPostgreSQLに関するエラーとその対処法をご紹介します。
やりたいこと
ある条件を満たすユーザに関する何らかの値の「平均値」、「合計値」、「合計ユーザ数」を返すメソッドを実装したい。
今回は年齢 (age
) が20歳のユーザの得点 (score
) の「平均値」、「合計値」、「合計ユーザ数」を返すメソッドを例にご紹介します。
仕様
対象のメソッド
class User < ActiveRecord::Base
.
.
.
def self.parameters()
parameters = User.where(age: 20)
.select('COUNT(*) AS total_user,
ROUND(AVG(score), 1) AS average,
SUM(score) AS total_score')
{
average: parameters.first.average || '0.0',
total_user: parameters.first.total_user,
total_score: parameters.first.total_score || 0
}
end
.
.
.
end
スキーマ
# == Schema Information
#
# Table name: users
#
# age :integer
# created_at :datetime not null
# id :integer not null, primary key
# score :integer
# updated_at :datetime not null
#
どんなエラー?
PG::GroupingError: ERROR: column "users.id" must appear in the GROUP BY clause or be used in an aggregate function
id
でGROUP BY
しろ?
対処法
.order(nil)
を追加すれば解決した。
class User < ActiveRecord::Base
.
.
.
def self.parameters()
parameters = User.where(age: 20)
.select('COUNT(*) AS total_user,
ROUND(AVG(rate_score), 1) AS average,
SUM(rate_score) AS total_score')
.order(nil) # これを追加
{
average: parameters.first.average || '0.0',
total_user: parameters.first.total_user,
total_score: parameters.first.total_score || 0
}
end
.
.
.
end
原因
parameters
は要素が1つの配列になり、その後.first
でその要素を取得しています。
しかし、Railsは賢いため、parameters
を取得する際に、.first
までを1つのクエリとして最適化して処理してしまいます(多分)。
よって、「.first
をするにはオーダーされていないからダメだよ!」ということになっていました。
ということで、order(nil)
を追加することでオーダーするつもりはないことをRailsに知らせてあげる必要があったみたいです。
結論
Railsは賢い()ので理解が微妙なまま開発を進めてはいけません。