SQL

【SQL】エンティティ・アトリビュート・バリューがなぜ悪いのか

今回はエンティティ・アトリビュート・バリュー(EAV)についてなぜアンチパターンなのかを見ていきたいと思います。

エンティティ・アトリビュート・バリュー(EAV)とは

EAVは可変属性をサポートする必要があるとき、凡庸的な属性を「行」に格納するパターンです。

CREATE TABLE Issues (
  issue_id SERIAL PRIVARY KEY
);
INSERT INTO Issues (issue_id) VALUES (1234);

CREATE TABLE IssueAttributes (
  issue_id BIGINT UNSIGNED NOT NULL,
  attr_name VARCHAR(100) NOT NULL,
  attr_value VARCHAR(100),
  PRIMARY KEY (issue_id, attr_name),
  FOREIGN KEY (issue_id, REFERENCES Issue_id)
);

INSERT INTO IssueAttributes (issue_id, attr_name, attr_value)
  VALUES
    (1234, 'product', '1'),
    (1234, 'date_reported', '2009-06-01'),
    (1234, 'status', 'NEW'),
    (1234, 'description', '保存処理に失敗する');

このような設計はオープンスキーマ、スキーマレス、名前/値ペアとも呼ばれます。
利点としては下記が挙げられ、一見すると良い設計に聞こえます。

  • テーブルの列数を減らせる
  • 新たな属性の定義にテーブルの列数を増やす必要がない
  • NULLだらけのテーブルになるのを防げる

なにが悪いのか

ここからはEAVの欠点を挙げていきます。

必須属性を設定できない

例えばdate_reported属性を必須属性にしたいとします。データベースの機能としてNOT NULL制約をかけるだけでその列を必須にすることができますが、EAVの設計ではそれができません。

型の指定ができない

例えばdate_repotedカラムに関係のない文字列’hoge’が入力されたらどうなるでしょうか。EAV設計の列のデータ型はあらゆる文字に対応するためにVARCHARで定義します。そのため、本来はじかなくてはいけない型のデータを許容してしまうことになります。

参照整合性を保証できない

データベースの従来の機能では外部キーを設定することによって、定義した属性の値を強制することができますが、EAVの設計では外部キーの設定を行うことができず、不正なデータの混入を許してしまいデータベースの中身を汚してしまう危険性があります。

属性名を補わなければならない

属性名の文字列に一貫性がない場合、それらすべてをサポートするクエリを書かないといけません。

SELECT date_reported, COUNT(*) AS bugs_per_date
FROM (SELECT DISTINCT issue_id, attr_value AS date_reported
  FROM IssueAttributes
  WHERE attr_name IN ('date_reported', 'report_date'))
GROUP BY date_reported;

行を再構築しなければいけない

下記表のように取得したい場合、各属性値でOUTER JOINしなければいけません。

issue_iddate_reportedstatusprioritydescription
12342021-09-05newhigh保存処理に失敗する

まとめ

EAV設計がデータベースの主な機能をほとんど使えないことがよく分かりました。
ここまで読んでくださりありがとうございました。

ABOUT ME
sakai
三重出身の28歳。前職はメーカーで働いていて、プログラミングスクールに通って未経験からWeb業界に転職しました。Railsをメインで使っていて、AWSも少しできます。音楽を聞くこととYoutubeを見るのが好きです。最近はへきトラ劇場にハマってます