【问题标题】:One to many dynamic database relations issue一对多动态数据库关系问题
【发布时间】:2012-05-20 15:26:29
【问题描述】:

我只需要确认一下这样设计的数据库是否正常。如果不是我在这里做错了什么。
我有以下表格:

TableA{TableAID,...}
TableB{TableBID,...}
TableC{TableCID,...}
etc.

我有一张桌子,我用它就像某种“新闻提要”。当我在任何表 A、B、C 中添加内容时,我也会在此表中添加行。

Feed{FeedID, TypeID, ReferenceID,...}

FeedID 是 PK 自动增量
TypeID 是引用类型表的数字,基于此 ID,我知道此表中的行来自表 A、B、C。
ReferenceId 是 ID表 A、B、C 中的项目。
A、B、C 表都有不同的字段。
现在,当我想获取提要数据时,我还需要从每个表中获取一些数据以在应用程序中使用它。在我的查询中,我使用了很多 SELECT CASE CLAUSE 之类的:

我首先加入查询中的所有表 (A,B,C)

...
CASE Feed.TypeId              
               WHEN 1 THEN tableA.someData
                           WHEN 2 THEN tableB.someData
                           WHEN 3 THEN tableC.someData
          END AS Data,
...

【问题讨论】:

  • 1:1 扩展表可用于相当复杂的查询。我更喜欢一张桌子。为与每种 Feed 类型不相关的属性提供可为空的列。
  • 为什么A、B、C是分开的表?为什么不拥有一个具有类型属性的表,而不是抽象它并使模型复杂化?如果您必须编写涉及基于表的附加列的查询,则这些查询可能会因涉及基于类型的附加列而同样复杂。换句话说,正如@Andomar 所建议的那样,所有可能的列都将在另一个表中,并且有些列可以根据类型为空。
  • 因为它们是独立的实体 A 可以有 10 列,B 可以有 3 列,C 可以有 5。

标签: sql-server database tsql database-design relational-database


【解决方案1】:

如果不考虑它是否适合特定目的,您的超类型-子类型模型是“颠倒的”。

所以 DDL 看起来像

CREATE TABLE Feed ( 
      FeedID               integer  IDENTITY(1,1) not null
    , FeedType             char(1)  not null
  --  Common_Columns_Here
    , Common_Column        varchar(20)
);
ALTER TABLE Feed ADD CONSTRAINT pk_Feed PRIMARY KEY (FeedID) ;


CREATE TABLE Feed_A ( 
      FeedID               integer  not null
  --  A_Specific_Columns_Here
    , A_Specific_Column    varchar(20)
);
ALTER TABLE Feed_A ADD
  CONSTRAINT  pk_Feed_A PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_A FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;


CREATE TABLE Feed_B (
      FeedID               integer  not null
  --  B_Specific_Columns_Here
    , B_Specific_Column    varchar(20)
);
ALTER TABLE Feed_B ADD 
  CONSTRAINT  pk_Feed_B PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_B FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;


CREATE TABLE Feed_C ( 
      FeedID               integer  not null
  --  C_Specific_Columns_Here
    , C_Specific_Column    varchar(20)
);
ALTER TABLE Feed_C ADD 
  CONSTRAINT  pk_Feed_C PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_C FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;

现在,为了从这个结构中读取,首先创建一个视图

create view vFeed as
select
      f.FeedID
    , FeedType
    , Common_Column
    , A_Specific_Column
    , B_Specific_Column
    , C_Specific_Column
from      Feed   as f
left join Feed_A as a on (a.FeedID = f.FeedID and f.FeedType = 'A')
left join Feed_B as b on (b.FeedID = f.FeedID and f.FeedType = 'B')
left join Feed_C as c on (c.FeedID = f.FeedID and f.FeedType = 'C')
;

看看当我想选择我知道来自提要 A 的数据时会发生什么。注意FeedType在这个查询中没有指定,只有属于Feed_A的列名(和普通列)。

select
      FeedID
    , Common_Column
    , A_Specific_Column
from vFeed;

注意执行计划只显示FeedFeed_A 表,查询优化器消除了_B_C 表;没必要碰那两个。

换句话说,您可以通过在查询中仅使用特定列来请求特定的提要数据,然后让优化器对其他所有内容进行排序——不需要您示例中的CASE ... WHEN .. acrobatics。

【讨论】:

  • 为什么你认为它被颠倒了?如果我做这样的设计,而不是当我得到所有提要时,我不知道要加入哪个表,因为我没有另一个表中的项目 ID。
  • @1100 加载此结构,首先插入Feed 表以获取FeedID,然后插入_A_B_C 之一。我会建议一个存储过程来处理这个问题。这样——通过读取和加载 sp 的视图——您将数据库模式(更改)与应用程序层隔离开来。您还可以在视图上使用 INSTEAD OF INSERT、UPDATE 触发器。
【解决方案2】:

正如我在评论中所建议的(以及@Andomar 的智慧),我认为这样的事情会更好:

CREATE TABLE dbo.FeedTypes
(
  FeedTypeID INT IDENTITY(1,1) PRIMARY KEY,
  SomedataA INT,
  SomedataB VARCHAR(32),
  SomedataC DATETIME
  --, ... other columns
);

CREATE TABLE dbo.Feeds
(
  FeedID INT IDENTITY(1,1) PRIMARY KEY,
  FeedTypeID INT NOT NULL FOREIGN KEY
    REFERENCES dbo.FeedTypes(FeedTypeID)
  --, ... other columns
);

您可以使用复杂的检查约束或触发器强制在给定类型的相关列中存在/不存在数据。但是,如果提要可以轻松更改类型,则您必须拥有相当复杂的逻辑(就像您在当前模型中那样)。

【讨论】:

    【解决方案3】:

    Feed 表的“新闻提要”中添加您希望显示的所有数据。这是重复的数据,但从长远来看,它会让你的生活更轻松。

    它还可以确保您的新闻源在历史上保持正确。这意味着当我更新三个表之一中的记录时,“旧”提要数据保持不变,而不是使用新值进行更新。

    【讨论】:

      猜你喜欢
      • 2011-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-30
      • 1970-01-01
      • 2015-09-14
      • 1970-01-01
      相关资源
      最近更新 更多