Railsマイグレーション完全入門|PostgreSQLの生成列(generated column)で集計を高速化しよう
生徒
「Railsでデータベースの計算を速くする方法ってあるんですか?毎回計算するのが大変そうで…」
先生
「ありますよ。PostgreSQLの生成列(generated column)を使うと、計算結果を自動で持たせられます。」
生徒
「計算結果を保存するんですか?それってズレたりしませんか?」
先生
「生成列は元の値から自動計算されるので安心です。Railsのマイグレーションで安全に使えますよ。」
1. Railsとマイグレーションの基本をおさらい
Railsは、Webアプリケーションを効率よく作るためのフレームワークです。MVCという考え方を採用しており、役割ごとにコードを分けて管理します。
マイグレーションとは、データベースの設計図をコードで管理する仕組みです。テーブル作成やカラム追加をRubyで書けるため、初心者でも安全にデータベース設計ができます。
Railsでは「どの環境でも同じスキーマになる」ことを大切にしており、マイグレーションがその中心的な役割を担っています。
2. スキーマ設計で重要な「計算をどこで行うか」
アプリでは、合計金額や税込価格など計算結果を扱う場面が多くあります。これを毎回Rubyで計算すると、データが増えたときに処理が重くなります。
そこで考えるのが「データベースに計算させる」という発想です。PostgreSQLにはgenerated column(生成列)という機能があり、これを使うと自動計算された値を列として持てます。
これは「電卓で毎回計算する」のではなく、「最初から答えが書いてある表を見る」イメージです。
3. PostgreSQLの生成列(generated column)とは?
生成列とは、他のカラムを元にして自動計算されるカラムです。自分で値を入れることはできず、常に計算結果が入ります。
PostgreSQLでは GENERATED ALWAYS AS (...) という書き方をします。これにより、データの整合性が保たれ、更新漏れが起きません。
RailsとPostgreSQLを組み合わせることで、安全かつ高速な集計が可能になります。
4. Railsマイグレーションで生成列を作る方法
Railsのマイグレーションでは、executeを使ってSQLを直接書けます。これによりPostgreSQL独自の機能も利用できます。
class AddTotalPriceToOrders < ActiveRecord::Migration[7.1]
def change
execute <<~SQL
ALTER TABLE orders
ADD COLUMN total_price integer
GENERATED ALWAYS AS (price * quantity) STORED;
SQL
end
end
この例では、priceとquantityからtotal_priceを自動計算しています。保存される値なので検索も高速です。
5. モデルから生成列を使うとどうなる?
生成列は通常のカラムと同じようにActive Recordから参照できます。ただし、代入はできません。
order = Order.new(price: 1000, quantity: 2)
order.save
order.total_price
2000
Ruby側で計算していないのに、正しい値が返ってくるのがポイントです。
6. 集計処理が高速になる理由
生成列はデータベースに保存されている値なので、検索や並び替えが非常に速くなります。
たとえば「合計金額が高い順に並べる」といった処理も、Rubyで計算せずに済みます。
Order.order(total_price: :desc).limit(5)
このように、RailsとPostgreSQLの役割分担を意識することで、アプリ全体が軽くなります。
7. generated columnを使うときの注意点
生成列は便利ですが、いくつか注意点があります。まず、対応しているデータベースはPostgreSQLなど一部に限られます。
また、Railsのschema.rbでは正しく表現されない場合があるため、structure.sqlを使う設定が推奨されます。
config.active_record.schema_format = :sql
これにより、生成列を含む正確なスキーマ管理が可能になります。