見出し画像

データベース設計で重要な、テーブルとカラムの設計を知ろう @TECH CAMP #16

 どうも、とだです。今日もデータベース設計についてアウトプットしようと思います。

テーブルの構成要素

 まずは、テーブルの構成要素について簡単にまとめます。
 また、エンティティについておさらいしておきます。エンティティとは、サービスの中で管理する必要のある概念、情報を指します。例えば、このnoteというサービスでは「ユーザー」や「投稿記事」、「コメント」などの情報を管理する必要があります。これらの情報がエンティティにあたります。

テーブルの行と列

 テーブルは名前の通りの形式で構成されています。エクセルの画面を思い浮かべるといいかもしれません。テーブルの行はレコード、列はカラムと言いますがそれぞれ表している意味が異なります。(念の為補足すると、行は横の並び、列は縦の並びです)

テーブルの行(レコード)はエンティティの具体的なデータを表す
テーブルの列(カラム)はエンティティの属性を表す

テーブルの行(レコード)
 レコードとはエンティティの具体的なデータです。例えば以下のようなユーザーテーブルのレコードを見てみましょう。

画像1


idが1である1行目は「とだ」というユーザーのデータを管理しています。idが2である2行目はやべさんのデータです。このようにレコードはそのテーブルの表す具体的なデータ(とだ、やべさん)を表しています。

テーブルの列(カラム)
 カラムとはエンティティの属性です。先ほどの例ですとユーザーテーブルにはid、name、email(それぞれ識別子、名前、メールアドレスの意味)という3つの属性を持っているということになります。

テーブル同士の関連性
 エンティティ間には関係性のある場合があります。「エンティティ = テーブル」と考えて良いので、テーブル同士にも関係性がある場合があります。この関係性がリレーションにあたります。
 例えば、ユーザーと投稿記事の間には関係性があります。ユーザーは投稿記事を持っており、投稿記事も必ずあるユーザーに紐付いています(とだはAという記事を投稿している、やべさんはBという記事を投稿しているなど)。このような場合、ユーザーテーブルと投稿記事テーブルの間にはリレーションがあります。

データを識別するための特殊な属性値

 属性の中にはキーと呼ばれる特殊なデータが存在します。キーは同じテーブルのレコード同士を識別する役割のあるデータです。多くの場合、idという名前のつく属性がキーとなります。キーは識別子であるので同じテーブル内の他のレコードとは絶対に被らないように設定します。キーには主キーと外部キーの2種類があります。

主キー
 
主キーは、テーブルの中で他のレコードとの区別をつける識別子となるカラムです。そのため、同じ主キーの値を持つレコードがテーブル内に存在してはいけません。多くの場合idという名前のカラムが主キーとなります。

画像1

 上のユーザーテーブルの場合、IDカラムが主キーになります。この時、やべさんのレコードのIDが1であってはいけません。

外部キー
 
外部キーは異なるテーブルのレコードと関係性(リレーション)を持つ場合に必要で、関連する他のテーブルのレコードの主キーを値として持つカラムカラムです。主キー同様に識別子の役割を持ちますが、他のテーブルのレコードを識別するため、また関係性を表すために使います。

画像1

画像4

 例えば、ユーザーテーブルと関係のある、投稿記事のテーブルがあるとします。投稿記事テーブルのidは主キーです。その他にuser_idという属性が存在します。投稿記事テーブルでは、このuser_idは外部キーに当たります。これはその記事を投稿したユーザーのレコードの主キーと対応しています。つまりユーザーテーブルのidが1であるレコードは投稿記事テーブルのidが2であるレコードと対応しており、このことから「とだが自己紹介というタイトルの記事を投稿した」ことが分かります。

安全なテーブルを設計するために必要な「制約」とは?

 テーブルのカラムに対して制約をかけることで不正なデータや予期せぬデータが保存されることを防ぐことが出来ます。制約とは特定のデータの保存をさせないために制限をかけること、つまりバリデーションです。例えば、同じメールアドレスのユーザーを登録できないようにする、名前のデータが空のユーザーを保存できないようにするといったことができるようになります。この辺は私たちもサービスを受けている中で体験したことがあることだと思うのでイメージがつきやすいと思います。制約には4つ種類があるので順にまとめていきたいと思います。

1. NOT NULL制約
 カラムに設定する制約で、絶対に値が必要のあるカラムに対して使います。
 NOT NULL制約はテーブルの属性値にNULL(空の値)が入ることを許さない制約です。例えば、usersテーブルのnameというカラムにNOT NULL制約を設定すると、nameが空(nil)レコードは保存できなくなります。

class CreateUsers < ActiveRecord::Migration
 def change
   create_table :users do |t|
     t.string :name, null: false
     t.string :e-mail, null: false
     t.timestamps null: false
   end
 end
end

 具体的には、上記のようなマイグレーションファイルにカラムを追加する時、「null: false」と追加して書いてあげることで空のデータを登録できないようにすることができます。

2. 一意性制約
 一意性制約はカラムに設定する制約で、テーブル内で重複するデータを禁止する制約です。一意性とはユニークで他とは違うという意味です。一意性制約を設定したカラムには同じ値を設定できなくなります。例えばAさんのemailが「test@example.com」だった場合、他にemailが「test@example.com」のレコードを保存できなくなります。
 テーブルのカラムに一意性制約をかけるときは、インデックスの作成も必要になります。全てのデータを検索しないと、過去のデータと重複しているかわからないからです。(インデックスについては後述します)
 Railsでは、add_indexメソッドの中で「unique: true」という引数を指定することで、一意性制約をかけるためのマイグレーションファイルを作成できます。

add_index :テーブル名, :カラム名, unique: true


3. 主キー制約
 主キー制約とは、レコードが必ず主キーを持っていなくてはいけないこと、かつ重複していないを保証するための制約です。主キーに対してNOT NULL制約と一意性制約を両方設定するのと同義になります。Railsでテーブルを作成する際、主キー制約は元々実装されています。Railsでは主キーはidカラムとして自動で作成されます。つまりidカラムの値は重複しないようにできています。

4. 外部キー制約
 外部キー制約とは、外部キーに対応するレコードが必ず存在することを保証する制約です。外部キーのカラムに値があっても、その値を主キーとして持つ他のテーブルのレコードがなければいけません。例えばuser_idが3のレコードを保存するためにはuserテーブルにidが3のレコードが存在していなくてはいけません。Railsでは、マイグレーションファイルで外部キーとなるカラムを追加するときに「foreign_key: true」と記述することで外部キー制約を設定することができます。

class CreateArticles < ActiveRecord::Migration
 def change
   create_table :article do |t|
     t.string :title, null: false
     t.text :contents, null: false
     t.references :user, foreign_key: true
     t.timestamps null: false
   end
 end
end

 マイグレーションファイルの中で上記のように記述してあげて、マイグレーションを実行するとArticlesテーブルにはuser_idというカラムが作成されています。このuser_idカラムが外部キーで、外部キー制約が設定されています。

インデックスとは

 インデックスはデータベースの機能の一つで、テーブル内のデータ検索を高速化することができます。インデックスはカラムに対して設定することができ、設定したカラムでの検索が高速になります。また、インデックスを設定することを、「インデックスを貼る」と言います。
 インデックスには、データ内のカラムの検索が高速化する反面、次のようなデメリットもあります。

データを保存・更新する速度が遅くなる
 データを保存する際に、設定されているインデックスの数だけ追加でデータを作成します。インデックスを設定するカラムが増えるだけ保存するデータが増え、処理の速度が遅くなってしまいます。

データベースの容量を使う
 インデックスはそのカラムで検索しやすいための特別なデータを保存するために検索速度が向上する仕組みです。そのため、インデックスを多く設定すればその分、データが必要になり容量が圧迫されます。

 やたらめったらインデックスを貼ってはいけないんですね。カラムの検索の高速化と、データの容量や、保存・更新速度とのバランスを考えないといけないということですね。

1つのカラムに対するインデックス

 テーブル内の1つのカラムにインデックスを貼る場合は、そのカラムで検索した場合に検索速度が向上します。インデックスはマイグレーションファイル内で以下のように記述することで設定することができます。

class AddIndexToテーブル名 < ActiveRecord::Migration
 def change
   add_index :テーブル名, :カラム名
 end
end

複数のカラムに対するインデックス

 インデックスは1つのカラムだけではなく、複数のカラムにも設定ができます。例えば、ユーザーを姓と名で検索するシステムを作っていることを想定しましょう。SQLは以下のようになります。

SELECT * 
FROM users
WHERE family_name = '山田' AND first_name = '太郎'

 このように検索時に2つのカラムを使う場合が多いときに複数カラムに対してインデックスを設定します。複数のカラムにインデックスを設定するためには、マイグレーションファイル内で以下のように記述します。

class AddIndexToテーブル名 < ActiveRecord::Migration
 def change
   add_index :テーブル名, [:カラム名, :カラム名]
 end
end

まとめ

 結構な量になりましたが、まとめると以下のようになります。

・エンティティ(サービスの中で管理する必要のある概念、情報)をテーブルとして定義する
・エンティティの持つ属性をカラムとして定義する
・カラムには主キーを必ず持たせる(Railsでは主キーはidカラムとして自動で作成される)
・他のテーブルのレコードと関連(リレーション)がある場合、外部キーという形で他のテーブルとの関係を保存する
・カラムの値には制約をつけてデータの正しさを保証する
・値が必ず設定されていることを保証するときにはNOT NULL制約を用いる
・値に重複がないように設定するには一意性制約を用いる
・キーの存在を保証するときには主キー制約、外部キー制約を用いる
・検索する際に使うカラムにはインデックスを設定する

終わりに

 前回と合わせて、データベース設計についての備忘録として残せるよう、頑張ってまとめました。わからなくなった時はここに戻ってきて学びなおすつもりです。



この記事が気に入ったらサポートをしてみませんか?