💬

月次のデータを管理するテーブルの設計例

2023/11/11に公開

想定しているケース

月次でユーザーのアクティビティを集計したり、売上を集計したりとか。

週次もほぼ同じパターンで設計できる。

色々なパターンを見てきたので自分なりの定型パターンを書いておく

Railsの機能を使うので他のフレームワークの場合は良くない設計かもしれない…

テーブル

  • beginning_of_month というdate型のカラムを用意し、月初の日付を入れて「月次」とする
name = 'monthly_user_activities'

create_table name, id: :bigint, unsigned: true, force: :cascade, collation: 'utf8mb4_general_ci', options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
  t.date "beginning_of_month", comment: "対象月(月初の日付を入れる)"
  t.bigint "user_id", unsigned: true, null: false, comment: "ユーザーID"
  t.integer "comment_count", unsigned: true, null: false, comment: "コメント数"

  t.datetime "created_at", null: false, comment: "作成日時", precision: nil
  t.datetime "updated_at", null: false, comment: "更新日時", precision: nil

  t.index ["beginning_of_month", "user_id"], name: "key_#{name}_1", unique: true
end

ActiveRecord

app/models/monthly_user_activity.rb
class MonthlyUserActivity < ApplicationRecord
  belongs_to :user

  validates :beginning_of_month,
            presence: true
  
  validates :comment_count,
            presence: true

  scope :on_month, ->(date) { where(beginning_of_month: date.beginning_of_month) }
end

上記のようなコードになる

now = Time.zone.now

MonthlyUserActivity.on_month(now).create!(
  user: user,
  comment_count: 123
)

みたいに使う。

Rails 7.1以降

normalizes が使える

app/models/monthly_user_activity.rb
class MonthlyUserActivity < ApplicationRecord
  belongs_to :user

  normalizes :beginning_of_month, with: -> (date) { date.beginning_of_month }

  validates :beginning_of_month,
            presence: true

  validates :comment_count,
            presence: true
end
now = Time.zone.now

MonthlyUserActivity.create!(
  beginning_of_month: now,
  user: user,
  comment_count: 123
)

その他の設計パターン

遭遇したことがあるのは以下のパターン

  1. '202311' みたいな文字列で管理する
  2. 202311 という整数で管理する
  3. year と month に分けて管理する

どれもメリット・デメリットはあると思うが、最終的には beginning_of_month を使うパターンに落ち着いた

beginning_of_week を使えば週次にもできて同じパターンで実装できる

Discussion