在表继承中强制执行排他性:复合外键与检查约束

时间:2014-06-03 21:24:11

标签: sql sql-server class-table-inheritance

我正在遵循Jeff Smith的“Implementing Table Inheritance in SQL Server”中描述的技术(这似乎是用于实现此类结构的事实上的方法)。 People基表与其三个子类型表StudentsTeachersParents具有1:0..1的关系。传统上,这是通过将子类型表的主键定义为基表的外键来完成的。

为了强制子类型之间的排他性(防止同一个人同时成为学生和教师),作者建议将PersonTypeID作为持久计算列添加到每个子类型表中,并将其包括在基表的外键约束。

CREATE TABLE PersonType 
(
    PersonTypeID INT PRIMARY KEY,
    Description  VARCHAR(10)
);

INSERT INTO PersonType 
VALUES (1, 'Student'),
       (2, 'Teacher'),
       (3, 'Parent');

CREATE TABLE People 
( 
    PersonID     INT PRIMARY KEY,
    PersonTypeID INT REFERENCES PersonType (PersonTypeID), 
    Name         VARCHAR(10),
    UNIQUE (PersonID, PersonTypeID)
)

CREATE TABLE Students 
( 
    PersonID INT PRIMARY KEY, 
    PersonTypeID AS 1 PERSISTED, -- student 
    EnrollmentDate DATETIME, 
    FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID) 
) 

CREATE TABLE Teachers 
( 
    PersonID INT PRIMARY KEY, 
    PersonTypeID AS 2 PERSISTED, -- teacher 
    HireDate DATETIME, 
    FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID) 
) 

CREATE TABLE Parents 
( 
    PersonID INT PRIMARY KEY, 
    PersonTypeID AS 3 PERSISTED, -- parents 
    DifficultyScore INT, 
    FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID) 
) 

然而,这种方法存在许多问题:

  1. 在每个子类型表上浪费了额外的空间列。
  2. 它需要在基表上增加一个唯一约束。这会浪费更多空间(因为它将作为唯一索引实现)并减慢对基表的更新。
  3. 外键约束涉及检查两列(而不是一列),从而减慢对子类型表的更新。
  4. 我的假设是,通过标量函数使用检查约束来强制执行唯一性会更好。这将消除额外列和唯一索引的浪费存储,加快对基表的更新,并希望实现与复合外键更新子类表相同的性能。

    CREATE TABLE People 
    ( 
        PersonID     INT PRIMARY KEY,
        PersonTypeID INT REFERENCES PersonType (PersonTypeID), 
        Name         VARCHAR(10)
    )
    
    CREATE FUNCTION GetPersonTypeID (@PersonID INT)
    RETURNS INT
    AS
    BEGIN
        RETURN 
        (
            SELECT PersonTypeID 
            FROM People
            WHERE PersonID = @PersonID
        )
    END;
    
    CREATE TABLE Students 
    ( 
        PersonID INT PRIMARY KEY REFERENCES People (PersonID) 
            CHECK (dbo.GetPersonTypeID(PersonID) = 1),
        EnrollmentDate DATETIME
    ) 
    
    CREATE TABLE Teachers 
    ( 
        PersonID INT PRIMARY KEY REFERENCES People (PersonID)
            CHECK (dbo.GetPersonTypeID(PersonID) = 2),
        HireDate DATETIME
    ) 
    
    CREATE TABLE Parents 
    ( 
        PersonID INT PRIMARY KEY REFERENCES People (PersonID)
            CHECK (dbo.GetPersonTypeID(PersonID) = 3),
        DifficultyScore INT
    ) 
    

    有没有理由不采用这种方法?

1 个答案:

答案 0 :(得分:1)

额外的存储空间实际上是最小的,如果您的人员类型列是一个tinyint(最多255种类型的人),您仍然只使用每人一个字节。因此,这不应该是决定中的一个重要因素,主要问题是标量udfs的性能明显低于外键约束。这已经过测试,结果显示在文章Scalar UDFs wrapped in CHECK constraints are very slow and may fail for multirow updates 中。

测试也包含在this SO answer