典型的な学校のデータベースのためにこのデザインを考えてください:
Person:
-----------------
FirstName
LastName
SocialSecurityNumber
Phone
Email
Student:
-----------------
Grade
Teacher:
-----------------
Specialty
ご覧のとおり、このデザインには3つのテーブルがあります。 1つのテーブルは抽象化に関する一般的な情報を保持し、他の2つのテーブルは具象エンティティに関する特定のデータを保持します。 Students
テーブルとTeachers
テーブルには、Persons
テーブルと1対1の関係があります。これまでのところ問題はありません。
ただし、この設計に基づいて、Persons
テーブルにId 30のレコードがある可能性があり、何らかの理由(データベースに対する手動スクリプトの実行など)のために、同じIdを持つ2つのレコードを挿入しますStudents
、およびTeachers
テーブル。このように、Id 30を持つ人は、教師であると同時に生徒でもあります。
まあ、それは学校のデータベースの文脈では理にかなっています。ただし、派生テーブルが相互に排他的であるというコンテキストもあります。そのため、1つの具象タイプのエンティティを、反対のタイプのエンティティにすることは論理的にできません。
階層型データベース設計で派生テーブル間で重複するId挿入を防ぐにはどうすればよいですか?トリガーでそれを実現できることはわかっていますが、コードはトリガーを使用することでにおいがすると思います。
注:私はSQL Serverを使用しており、Entity Frameworkはこのデザインをタイプごとのテーブル(TPT)と呼んでいます。
生徒/教師の属性を個人に追加します。この属性は、Personのキーに依存するため(それが何であれ)、Personが教師と生徒の両方になることはできません。今では、タイプが生徒である人が先生などに追加されないことを保証することです。
チェック制約のクエリをサポートするDBMSの場合、次のようなことができます。
ALTER TABLE Student Add constraint ...
CHECK ( (select type
from person p
where p.<key> = <key>) = 'Student' )
DBMSがこのタイプの構築をサポートしていない場合は、主キーとtype属性で構成されるスーパーキーをPersonに追加できます。 type属性をTeacherとStudentに追加し、それらの「サブテーブル」の型を保証するチェック制約と、type属性を含む外部キーを追加します。
ALTER TABLE Person ADD COLUMN type_attribute varchar(..) not null;
ALTER TABLE Person ADD CONSTRAINT ... UNIQUE (<key>, type_attribute);
ALTER TABLE Student ADD COLUMN type_attribute varchar(..) not null;
ALTER TABLE Student ADD CONSTRAINT ... CHECK (type_attribute = 'Student')
ALTER TABLE Student ADD CONSTRAINT ...
FOREIGN KEY (<key>, type_attribute)
REFERENCES Person (<key>, type_attribute);
これで、生徒を教師として追加したり、その逆を行うことはできなくなりました。その属性を持つ人のための学生/教師が本当にいることは、情報を追加するトランザクションを通じて保証される必要があります。
1つの方法は、PersonType属性を導入することです。これを複合キーとして、StudentテーブルとTeacherテーブルのチェック制約と外部キーとともに使用すると、特定の個人の行がこれらのエンティティの1つと適切なテーブルにのみ存在するようになります。この実装がEFとどの程度うまく機能するかについてはお話しできません。
代替キーを使用した同様のアプローチがあります(一意の制約)。外部キーは、主キーの列だけでなく、一意の制約/インデックス列を参照できます。
CREATE TABLE dbo.PersonType(
PersonTypeCode char(1) NOT NULL
CONSTRAINT PK_PersonType PRIMARY KEY
, TypeName varchar(30) NOT NULL
);
INSERT INTO dbo.PersonType (PersonTypeCode, TypeName)
VALUES('S', 'Student'), ('T', 'Teacher');
CREATE TABLE dbo.Person(
PersonID int NOT NULL
, PersonTypeCode char(1) NOT NULL
CONSTRAINT FK_Person_PersonType
FOREIGN KEY REFERENCES dbo.PersonType(PersonTypeCode)
, FirstName varchar(50) NOT NULL
, LastName varchar(50) NOT NULL
, SocialSecurityNumber char(11) NOT NULL
CONSTRAINT UN_Person_SocialSecurityNumber UNIQUE
, Phone varchar(20) NOT NULL
, Email nvarchar(255) NOT NULL
, CONSTRAINT PK_Person
PRIMARY KEY(PersonID, PersonTypeCode)
);
CREATE TABLE dbo.Student(
PersonID int NOT NULL
, PersonTypeCode char(1) NOT NULL DEFAULT 'S'
CONSTRAINT CK_Student_PersonType CHECK (PersonTypeCode = 'S')
, Grade char(2) NOT NULL
, CONSTRAINT PK_Student
PRIMARY KEY(PersonID, PersonTypeCode)
, CONSTRAINT FK_Student_Person
FOREIGN KEY(PersonID, PersonTypeCode)
REFERENCES dbo.Person(PersonID, PersonTypeCode)
);
CREATE TABLE dbo.Teacher(
PersonID int NOT NULL
, PersonTypeCode char(1) NOT NULL DEFAULT 'T'
CONSTRAINT CK_Teacher_PersonType CHECK (PersonTypeCode = 'T')
, Specialty varchar(20) NOT NULL
, CONSTRAINT PK_Teacher
PRIMARY KEY(PersonID, PersonTypeCode)
, CONSTRAINT FK_Teacher_Person
FOREIGN KEY(PersonID, PersonTypeCode)
REFERENCES dbo.Person(PersonID, PersonTypeCode)
);