【问题标题】:Modeling many-to-one with constraints?用约束建模多对一?
【发布时间】:2011-02-17 14:47:47
【问题描述】:

我正在尝试为电影分类创建一个数据库模型,其中每部电影都可以从多个评级系统(例如 BBFC、MPAA)中的每一个中获得一个分类。这是当前的设计,包含所有隐含的 PK 和 FK:

TABLE Movie 
( 
    MovieId INT -- PK
)

TABLE ClassificationSystem 
( 
    ClassificationSystemId TINYINT -- PK
)

TABLE Classification 
(
    ClassificationId INT,          -- PK
    ClassificationSystemId TINYINT -- FK
)

TABLE MovieClassification 
(
    MovieId INT,          -- composite PK, FK
    ClassificationId INT, -- composite PK, FK
    Advice NVARCHAR(250)  -- description of why the classification was given
)

问题在于MovieClassification 表,其约束允许来自同一系统的多个分类,而理想情况下它应该只允许来自给定系统的零或一个分类。

在满足以下要求的情况下,是否有任何合理的方法来重构它,以便通过数据库约束强制执行来自任何给定系统的具有零或一个分类的电影?

  • 不要复制可以查找的信息(即在MovieClassification 表中复制ClassificationSystemId 不是一个好的解决方案,因为这可能与Classification 表中的值不同步)
  • 保持可扩展到多个分类系统(即新分类系统不需要对表结构进行任何更改)?

另请注意Advice 列 - 电影到分类的每个映射都需要有文字说明,说明为什么将分类给予该电影。任何设计都需要支持这一点。

【问题讨论】:

  • @Marcelo - 刚刚添加了明确的注释

标签: sql-server database-design sql-server-2008 normalization referential-integrity


【解决方案1】:

您可以通过调用用户定义函数的检查约束来强制执行此操作。例如:

create function dbo.ClassificationSystemCheck()
returns int
as begin
    return (select max(cnt)
    from (
        select count(*) as cnt
        from MovieClassification mc
        left join Classification c
        on c.ClassificationId = mc.ClassificationId
        group by mc.MovieId, c.ClassificationSystemId
    ) qry)
end
go
alter table MovieClassification
add constraint chk_MovieClassification
check (dbo.ClassificationSystemCheck() <= 1)
go
alter table Classification
add constraint chk_Classification
check (dbo.ClassificationSystemCheck() <= 1)
go
insert into Classification select 1,1
insert into MovieClassification select 1,1
insert into MovieClassification select 1,1 -- Boom!

随着分类数量的增加,这可能会降低效率。或者,您可以删除 Classification 表并将 ClassificationSystemId 移至 MovieClassification 表。

【讨论】:

    【解决方案2】:

    如果您从分类表中删除分类系统 id 并仅将其保留在电影分类中怎么办?

    TABLE Movie 
    ( 
        MovieId INT
    )
    
    TABLE ClassificationSystem 
    ( 
        ClassificationSystemId TINYINT
    )
    
    TABLE Classification 
    (
        ClassificationId INT,
    )
    
    TABLE MovieClassification 
    (
        MovieId INT,
        ClassificationId INT,
        ClassificationSystemId TINYINT,
        Advice NVARCHAR(250) -- description of why the classification was given
    )
    

    但是您会发现另一个问题,即分类可以在其预期系统之外使用。

    【讨论】:

    • 我认为这是在以更坏的问题交换一个问题;一个分类属于一个系统,这无法明确确定一个分类属于哪个系统。
    【解决方案3】:

    在你的设计下,同一个分类系统有多少不同的分类可以归于一部电影?

    这是否符合您预期的“分类”概念?

    【讨论】:

    • 这应该是评论,而不是答案。
    • "每部电影都可以从多个评分系统中的每一个中获得一个分类"
    【解决方案4】:

    根据您的说法,ClassificationSystemIdMovieClassification 的密钥的一部分,因为对于给定电影的给定系统,只能有一个(或零个)MovieClassification

    现在,Classification 表可以更改的三种情况:

    1. 您将新分类添加到系统中。
    2. 您从系统中删除了现有分类。
    3. 您更改了分类的元数据(未在您发布的架构中显示)。

    在第一种情况下,一个示例是向现有流派系统添加新流派。您需要对属于新类型的电影重新分类是有道理的,因此该模型成立。

    在第二种情况下,一个例子是从现有系统中删除一个流派。您需要对属于旧类型的电影重新分类仍然是有道理的,因此该模型仍然成立。

    在第三种情况下,您将更改例如流派的名称。已经归类为该类型的电影更改其类型名称是有道理的。该模型仍然成立。

    据我所知,正确的规范化是将ClassificationSystemId 放入MovieClassification 并使其成为MovieClassification 键的一部分(并使ClassificationSystemId 成为提供的Classification 行的键的一部分架构):

    -- Tables Movie, ClassificationSystem not included for brevity
    
    CREATE TABLE Classification
    (
      ClassificationId INT,
      ClassificationSystemId INT,
      PRIMARY KEY(ClassificationId, ClassificationSystemId),
      FOREIGN KEY(ClassificationSystemId) REFERENCES ClassificationSystem(ClassificationSystemId)
    );
    
    CREATE TABLE MovieClassification
    (
      ClassificationId INT,
      ClassificationSystemId INT,
      MovieId INT,
      Advice NVARCHAR(MAX),
      PRIMARY KEY(ClassificationId, ClassificationSystemId, MovieId),
      FOREIGN KEY(ClassificationId, ClassificationSystemId) REFERENCES Classification(ClassificationId, ClassificationSystemId),
      FOREIGN KEY(MovieId) REFERENCES Movie(MovieId),
      UNIQUE(ClassificationSystemId, MovieId)
    );
    

    【讨论】:

    • 我的意思是,如果您更改 Classification 中的“主”版本,则任何信息副本也不会得到更新。当然,您可以强制执行此操作,但这并不能购买任何东西,因为您可以强制检查当前的约束。
    • 嗯,这对我来说真的没有意义。如果您更改 Classification 中的“主”,您要么添加新分类,要么删除现有分类(或更改现有分类的列数据)。据我所知,在所有情况下,这种关系在没有任何强制执行的情况下仍然成立。
    【解决方案5】:
    1. 为分类添加唯一约束(ClassificationId,ClassificationSystemId)
    2. 从 MovieClassification 表中添加一个引用它的 FK
    3. 在 MovieClassification (MovieId,ClassificationSystemId) 上添加唯一约束

    【讨论】:

      【解决方案6】:

      好的。我曾希望我的问题会引发一些思考,但我的观点似乎被忽略了。

      您的分类表需要为 {movieID, classificationScheme 分类},键为 {movieID classificationScheme}。

      可以通过{movieID}引用电影,可以通过{classificationScheme分类}引用分类表。

      此分类表列出/枚举/命名每个方案的所有有效分类。由于分类方案仅存在并且有意义,从它具有至少一个分类的那一刻起,可能并不真正需要第四个表,其唯一目的是列出/命名/枚举所有相关的分类方案。

      【讨论】:

        【解决方案7】:

        老实说,我只是稍微改变一下数据模型。

        create table #Movies (PK_moID int identity(1,1), moName varchar(50) primary key(PK_moID))
        create table #ClassificationSystem (PK_csID int identity(1,1), csName varchar(50) primary key(PKcsID))
        create table #Classification (PK_clID int identity(1,1), FK_csID int)
        create table #MovieClassification (FK_moID int, FK_csID int, FK_clID int primary key (FK_moID, FK_csID))
        

        现在,通过您的电影分类,您可以获得电影和系统的复合 pk,因此每个系统只能获得一个电影评分(即使您添加了新系统)。您还可以创建从电影分类到分类表的关系,以添加数据。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-08-08
          • 2018-06-20
          • 2010-12-22
          • 1970-01-01
          • 1970-01-01
          • 2011-09-10
          • 2016-12-20
          相关资源
          最近更新 更多