Edited at
YiiDay 20

DB スキーマ設計のガイドライン

More than 1 year has passed since last update.

この記事は、2011年頃に書かれた Yii framework サイトの wiki 記事 Guidelines for good schema design の翻訳です。

もともとは Yii 1.1 のために書かれたものですが、Yii 2.x, 3.x にもそのまま適用可能ですし、もっと広く、アクティブ・レコードのような ORM 一般に通用する内容であろうと思われます。つまり、以下の文章中の "Yii" という名前は、あなたが使っている任意のフレームワークの名前に置き換えてもよい筈です。

ですから、この記事が「最終更新日から1年以上が経過」していようと、気にすることはありません。


はじめに

事実上、すべての Yii アプリケーションはデータベースの上に構築されます。Yii は、データベースのテーブル名やコラム名に関して、非常に柔軟に対応出来ます。それでも、より一層ものごとを楽にしてくれる設計上の選択とでも言うべきものはあります。

最初に、ごく大まかに言うと、Yii アプリケーションではアクティブ・レコードを多用しますので、設計上の考慮では、複雑な SQL クエリを書くことを好む人間のことよりも、アクティブ・レコードを使う上での最適化を中心に置くべきです。実際、以下に述べる設計のガイドラインの多くは、SQL フレンドリーなスキーマを作成するための良い習慣とは相反するものです。

目的は他人が読んで理解できるコードを書けるようにすることです。名前は 意味を持つ ものですから、良い名前を付けることが非常に重要です。また、命名規則に一貫性が無いと、コードを追うのがますます困難になります。

このことはフォーラムやチャットの #yii チャンネルで助力を求める場合に特に当てはまります。正しい意味を反映しない変な名前を使っていると、コードが何をやっているのかを明確にするための逆質問ばかりを沢山もらって、実際の問題に対してはあまり助力を得られないということになりかねません。

一貫性は非常に大事です。

以下はガイドラインであり、規則ではありません。これらのガイドラインに従わなくても、コードは動きます。しかし、これらのガイドラインを採用する方が、楽な道を歩むことが出来ます。


テーブル名は単数形にする、複数形にしない

テーブル は事物の複数のインスタンスを保持するものですが、モデル は単数形の事物です。$model = new Comments() という記述は何か変です。この違和感が、リレーションを定義するときをはじめとして、どこまでも付いて回ることになります。

テーブルは comments でなく comment, invoices でなく invoice としましょう。そして対応するモデル名も単数形 (Comment, Invoice) にします。

どうしても DB スキーマを変更できない場合は、少なくとも、Yii のモデル・クラスの名前を単数形に変更します。ただし、コード中に追加のコメントを記して、この不整合に対する注意を喚起しておきましょう。


フィールド名にテーブル名を追加しない

これは伝統的な SQL スキーマ設計では当り前の習慣ですが、アクティブ・レコードを使う場合には、鬱陶しいものになります。たとえば、category テーブルでは、こうです。


category.sql

-- 駄目                            -- おっけ

create table category ( create table category(
category_id INTEGER ..., id INTEGER ...,
category_name VARCHAR ..., name VARCHAR ...,
category_value INTEGER value INTEGER
); );


Category.php

<?php

$category = new Category();
// ぐえ // 良いよね
$category->category_id $category->id
$category->category_name $category->name
$category->category_value $category->value

長い名前のやり方は、手作業で SQL クエリを組む場合にちょっとだけ読みやすくなるのは事実ですが、アクティブ・レコードで使うのには向いていません。


モデルのクラス名にテーブル・プレフィックスを含めない

Yii はテーブル・プレフィックスの考え方をサポートしています。これは共有ホスト環境で すべての アプリケーションが単一のデータベースを共有する場合に有用なものです。例えば、ブログのテーブル名の頭には blog_ を付ける、時間管理のテーブル名の頭には time_ を付けるなどすると、同じデータベースの中でお互いに干渉することなく動作させることが出来ます。

tbl_ というプレフィックスは、多くのチュートリアルやサンプルで見かけます。

しかし、クラス名 にはこれらのプレフィックスを決して含めるべきではありません。なぜなら、干渉を回避する必要が無いからです。ブログのアプリケーションは、時間管理のアプリケーションとは全くの別物です。


Comment.php

<?php

class TblComment extends CActiveRecord { // 駄目
class Comment extends CActiveRecord { // おっけ

コードの至る所でプレフィックスを目にするのは気が散る元です。


テーブル自体の ID カラムは "id" という名前にする

たいがいのテーブルは、それ自体の独立した単一カラムのユニークなプライマリ・キーを持っています(int NOT NULL AUTO_INCREMENT PRIMARY KEY というのがよくある例です)。これが id という名前 (commentid とか postid とかではなく) を持っている方が、物事がほんの少しスムーズに動くようになります。

名前が何であっても、Yii はデータベース・スキーマを読んでプライマリ・キーが何であるかを正しく判断します。しかし、システムの他の部分ではそれに従うことが出来ず、キーが id であることを明らかに前提としている場合もあります。

例 : CArrayDataProvider はキーが id であると見なしています。これは keyField 属性によってオーバーライド出来るものではありますが、そもそも、そういう事をする必要が無い方が好都合です。

ただし、この規則が当てはまらない場合もいくつかあります。例えば、テーブルがマルチ・カラムのプライマリ・キーを持っている場合や、テーブルのプライマリ・キーが他のテーブルの ID への外部キーである場合などです。


意味を持つフィールドをプライマリ・キーにしない

古典的な設計上の失敗の一つとして、実際の意味を持つプライマリ・キー、というのがあります。以下の例では、username が user テーブルのプライマリ・キーになっています。


user.sql

-- これやっちゃ駄目!

CREATE TABLE user (
name VARCHAR(16) PRIMARY KEY, -- やめれ
email VARCHAR...
...
)

これをやると、面倒なことが二つ起きます。


  1. これは 4 バイトの整数でなく、16 文字のフィールドであるため、他からの 参照 が非常に非効率的になります。このことは、多くの参照を持つ規模の大きいアプリケーションでは、現実的なパフォーマンス上の問題になり得ます。

  2. 外部キー制約がこのシステムで有効になっている場合 ユーザ名の変更 が非常に困難になります。テーブルのフィールドとそれに対するすべての参照を同時に変更しなければならず、仮にそれが可能であったとしても、非常に高く付く処理になります。

整数のプライマリ・キーを作って、name をユニークにする方が断然すぐれています。


user.sql

-- はるかに良い

CREATE TABLE user (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL UNIQUE,
email VARCHAR...
...
);

こうすれば、ユーザ名の変更は 1 レコードの更新だけで済みます。


データベース・スキーマで外部キー制約を定義する

たいていのデータベースでは、このフィールドは別のテーブルのプライマリ・キーを指し示す ID を保持している、というような、テーブル間の関係を定義することが出来ます。これが外部キー制約と呼ばれるもので、他から参照されている行が削除されるのを防止するなどして、参照一貫性を保持する上で役に立ちます。

MySQL の InnoDB は外部キー制約をサポートしています。MyISAM では、外部キー制約は 定義 することは出来ますが、強制 することは出来ません。Yii はスキーマからこういう関係を読み取る方法を知っており、Gii/Giix ツールが適切なリレーションを自動的に作成してくれます。

しかし、たとえ Yii が外部キー制約を考慮しないとしても、外部キー制約はデータベースの参照一貫性を維持するための不可欠な要素です。これについて学ぶためには、ウェブ上に沢山のチュートリアルがありますので参照して下さい。


外部キーのフィールドは "id" で終る名前にする

前項と関連しますが、他のテーブル、例えば user の ID を保持するフィールドには user ではなく userid という名前を付けます。と言うのは、まず間違いなく、テーブルに含まれるすべての外部キーについて、それに対応する リレーション を定義する必要があるからです。

Yii では、クラス変数、DB フィールド、仮想属性、リレーションは単一の名前空間を共有します。従って、$model->user をテーブルの外部キー および リレーションの 両方 を示すものとして定義することは出来ません。

外部キーを userid という名前にすれば、BELONGS_TO リレーションである $model->user が自然で分りやすい名前になります。


Post.php

<?php

class Post extends CActiveRecord {
public function relations()
{
return array(
'user' => array(self::BELONGS_TO, 'User', 'userid')
);
}

メモ : id でなく Id_id を好む人もいます。これは純然たる好みの問題です。ただし一貫性を失わないようにしましょう。


リレーションはその性質に応じた単数形/複数形の名前にする

一貫性と他人にとってのコードの読みやすさというテーマに沿って、リレーションもそれぞれの性質に応じた単数形/複数形の名前を持つべきです。



  • HAS_ONE - 単一のモデルを返す : 単数形


  • BELONGS_TO - 単一のモデルを返す : 単数形


  • HAS_MANY - モデルの配列を返す : 複数形


  • MANY_MANY - モデルの配列を返す : 複数形

配列を返すリレーションは、モデルを一つだけ返す場合であってもそれを 配列 に入れて返します。この事実が複数形の名前の根拠となることに注意して下さい。

コードを見ただけで、リレーションが配列を返すのか単一のモデルを返すのか、区別できるようにすべきです。

<?php

$model->post
$model->comments
$model->author
$model->members

定義を見ないと分らないと言うようでは、コードの可読性と保守性を大いに低下させていると言わざるを得ません。