一个可以引用多种类型对象的对象应该如何在关系模式中建模?

JKo*_*sin 6 mysql database-design referential-integrity

为这个晦涩的问题道歉,用一个具体的例子可能更有意义:

在我的应用程序中,我可能会创建一个包含一组项目的投资组合。但是,我也可以添加一个嵌套在投资组合(然后可能包含项目)中的“子投资组合”。目前,我通过以下方式绘制了我的架构:

+------------+    +-----------------------+    +------------+
| Portfolios |    | Portfolio_project_map |    | Projects   |
+------------+    +-----------------------+    +------------+
|   port_id  |    |   parent_id           |    |   proj_id  |
+------------+    +-----------------------+    +------------+
|            |    |   child_id            |    |            |
                  +-----------------------+
                  |   child_is_portfolio  |
                  +-----------------------+
Run Code Online (Sandbox Code Playgroud)

这看起来非常难看。在关系数据库中建模这种关系的正确方法是什么?还是真的没有,这样的细节应该在业务逻辑中严格执行?它甚至有可能对指定的外键约束Portfolio_project_map,使得child_id引用要么port_id还是proj_id

另外,这样的问题到底叫什么?我发现在几个地方引用了“一对一”,但我有一种不完全正确的感觉?

Con*_*lls 2

如果关系是严格分层的(即一个项目组合可以有多个子项目组合或项目,但一个项目不能出现在多个子项目组合中),那么您可以使用子类型模式对项目组合和项目进行建模,例如

-- === PortfolioItem  table ===========================================
-- Supertype table that records the relationships between portfolio 
-- items.
create table PortfolioItem (
       PortfolioItemID      int not null
      ,PortfolioItemRef     varchar (20) not null 
      ,PortfolioItemType    varchar (10) not null -- either 'PROJ' or 'PORT'
      ,ParentItemID         int -- Null for root
)
go

alter table PortFolioItem
  add constraint PK_PortfolioItem
      primary key nonclustered (PortfolioItemID)
go

alter table PortfolioItem
  add constraint UQ_PortfolioItem
      unique nonclustered (PortfolioItemRef, PortfolioItemType)
go


-- Parent-child relationship
--
alter table PortfolioItem
  add constraint FK_PortfolioItem_Parent
      foreign key (ParentItemID)
      references PortfolioItem
go



-- === PortfolioSubType ===============================================
-- This subclass table joins against the unique identifier but the 
-- check constraint restricts it to joining against 'portfolio' nodes
--
create table PortfolioSubType (
       PortfolioItemRef      varchar (20) not null
      ,PortfolioItemType     varchar (10) not null
      -- Portfolio attributes
)

alter table PortfolioSubType
  add constraint PK_PortfolioSubType
      primary key nonclustered (PortfolioItemRef, PortfolioItemType)
go

-- Cab only join against portfolio parent items
--  
alter table PortfolioSubType
  add constraint CK_Portfolio_Type
      check (PortfolioItemType = 'PORT')    
go

alter table PortfolioSubType
  add constraint FK_PortfolioSubType_SuperType
      foreign key (PortfolioItemRef, PortfolioItemType)
      references PortfolioItem (PortfolioItemRef, PortfolioItemType)
go


-- === ProjectSubType =================================================
-- This subclass table has the project specific items and a check 
-- constraint that prevents it from joining against parent nodes
-- that represent portfolios
--
create table ProjectSubType (
       PortfolioItemRef      varchar (20) not null
      ,PortfolioItemType     varchar (10) not null
      -- Project attributes
)

alter table ProjectSubType
  add constraint PK_ProjectSubType
      primary key nonclustered (PortfolioItemRef, PortfolioItemType)
go

-- Check constraint restricts this to projects
--
alter table ProjectSubType
  add constraint CK_Portfolio_Type
      check (PortfolioItemType = 'PROJ')    
go

alter table ProjectSubType
  add constraint FK_ProjectSubType_SuperType
      foreign key (PortfolioItemRef, PortfolioItemType)
      references PortfolioItem (PortfolioItemRef, PortfolioItemType)
go
Run Code Online (Sandbox Code Playgroud)

您可以强制执行完整性规则,以防止项目节点拥有具有类似于以下触发器的子节点:

-- === Trigger to enforce integrity ===================================
-- The trigger prevents project nodes from having children.
--
create trigger ProjectNodeIntegrity 
    on PortfolioItem
   for insert, update
as
    if exists 
        (select 1
           from PortfolioItem p_i
           join inserted i
             on i.ParentItemID = p_i.PortfolioItemID
            and p_i.PortfolioItemType = 'PROJ') begin
        raiserror ('Only portfolios may have children', 16, 1)
        rollback transaction
        return
    end
go
Run Code Online (Sandbox Code Playgroud)

这将反弹在项目节点下插入子节点的尝试。