133
113

More than 5 years have passed since last update.

[Rails] [RDB] キー・複合キーをきちんと利用する

Last updated at Posted at 2016-08-31

環境

Rails 5.0.0.1, MySQL 5.6

概要

Railsでは主キーは基本的にidをサロゲートキーとして用いる。

そのことの良し悪しはここでは論じないが、自然キーや複合キーがあるのであれば、それを意識し、正しく扱うことが重要であることは変わらない。

といっても基本的にはunique indexはるだけではある、それについてのメモ。

本編

ユーザが本質的に1日1回しかできない投稿がある、という例で考える。
users と daily_posts というテーブルがある。
usersのテーブル定義は省略し、daily_postsのmigrationファイルを正しく作る。

まず複合キーのことを考えずに、daily_postsのmigrationを作るとこんな感じになる。(作っただけでまだ db:migrate していない状態。)

def change
  create_table :daily_posts do |t|
    t.references :user, foreign_key: true, null: false
    t.date :date, null: false
    t.string :title, null: false

    t.timestamps
  end
end

1. キーを認識する

「本質的にuserが1日1回しかpostできない」ので、user_id + date は複合キーである。

これに対して、忘れずにunique indexをはるのが重要である。

注意: indexは、リレーショナルモデルに直接関係ない。一方、キーや複合キーはリレーショナルモデル上の重要な概念であり、それに「unique制約をつける」のが重要である。
unique制約のために、実質的にindexが必要であるから、unique indexをはる。

(ここまで書いて思ったが、unique制約は a + b だけどindexは a -> b -> c ではりたいときに、railsでどうできるのかは知らない、、)

2. (複合キーの場合) unique indexの順序を決める

理論的には user_id -> date でも date -> user_id でもどちらでも良い。使えそうな順序にする。
今回は user_id -> date にする。

3. キーに対してunique indexをはる

migrationに、 add_index :table_name, [:key1, :key2, ...], unique: true と書くことで実現できる。

def change
  create_table :daily_posts do |t|
    t.references :user, foreign_key: true, null: false
    t.date :date, null: false
    t.string :title, null: false

    t.timestamps
  end

  add_index :daily_posts, [:user_id, :date], unique: true
end

4. 無駄なindexが残っていないか注意する

複合のunique indexをはると、元あったindexが無駄になることがある。

上の例では、

  • t.references は、外部キーをはりながらindexをはってくれる機能。
  • t.references :user により、 user_id に対しindexがはられている。
  • user_id -> date のindexをはった。したがって user_id だけのindexは無駄である。

よって、 t.referencesをやめて、単に外部キーをはるだけにする。
すなわち、普通にカラムを定義したあとに add_foreign_keyをするように変更する。

def change
  create_table :daily_posts do |t|
    t.integer :user_id, null: false
    t.date :date, null: false
    t.string :title, null: false

    t.timestamps

  end

  add_index :daily_posts, [:user_id, :date], unique: true
  add_foreign_key :daily_posts, :users
end

追記: 以下のように、t.referencesにindex:falseのオプションを追加しても問題ありません。こちらのほうが簡単だったかもしれません。

def change
  create_table :daily_posts do |t|
    t.references :user, foreign_key: true, index: false, null: false
    t.date :date, null: false
    t.string :title, null: false

    t.timestamps
  end

  add_index :daily_posts, [:user_id, :date], unique: true
end

まとめ

テーブル設計=>migration作る=>unique indexはる=>不要なindexが生成されないか注意。

unique制約はテーブル設計のうちなので、migration作った段階で必ずはるようにすると良いと思う。

133
113
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
133
113