如何在 SQL 表中实现这种数据结构

Posted

技术标签:

【中文标题】如何在 SQL 表中实现这种数据结构【英文标题】:How to implement this data structure in SQL tables 【发布时间】:2012-06-05 20:50:25 【问题描述】:

我有一个问题可以总结如下:

假设我正在实现一个员工数据库。对于每个人取决于他的职位,应填写不同的字段。因此,例如,如果员工是软件工程师,我有以下列:

Name
Family
Language
Technology
CanDevelopWeb 

如果员工是业务经理,我有以下列:

Name
Family
FieldOfExpertise
MaximumContractValue
BonusRate

如果员工是销售人员,那么其他列等等。

如何在数据库架构中实现这一点?

我认为的一种方法是有一些相关的表:

核心表:

Name
Family
Type

如果类型是 1,则员工是软件开发人员,因此其余信息应在表 SoftwareDeveloper 中:

Language
Technology
CanDevelopWeb 

对于业务经理,我有另一个包含列的表:

FieldOfExpertise
MaximumContractValue
BonusRate

这种结构的问题是我不确定如何在表之间建立关系,因为一个表与一列上的多个表有关系。

如何强制执行关系完整性?

【问题讨论】:

我可以提供一些反馈吗?你这样做是完全错误的。不要试图从一开始就把它们拆散。对需要的内容进行分类。例如,Manager 继承 Employee,并使用更多字段扩展基本字段。技能集是一个字符串数组(存在表示布尔值)等。不要让它们如此依赖于表格,专注于您拥有的实际需求,以及如何在应用程序中最好地呈现它们。一旦你有了它,我们就可以向你展示如何将它们魔术到表中(EAV 用于 CanDevelopWeb 等功能)和 Manager:Employee 上的继承(通过传播)。 创建基于代码的类,然后将它们转换到数据库。不要,看在皮特的份上,不要先做桌子。制作桌子来存储东西就像我做的最后一件事。毕竟我的 UI、课程、设计、纸和铅笔画,以及其他一切。 How can you represent inheritance in a database?的可能重复 你的问题可能会有答案here 【参考方案1】:

这里有一些思想流派。

(1) 将可为空的列存储在单个表中并仅填充相关的列(检查约束可以在此处强制执行完整性)。有些人不喜欢这个,因为他们害怕 NULL。

(2) 您的多表设计,其中每种类型都有自己的表。使用 DRI 更难强制执行,但对于应用程序或触发逻辑可能微不足道。

其中任何一个的唯一问题是,一旦您添加了新属性(如 CanReadUpsideDown),您必须进行架构更改以适应该问题 - 在 (1) 中,您需要添加一个新列并一个新的约束,在 (2) 中,如果它代表一个新的员工“类型”,则需要添加一个新表。

(3) EAV,其中您有一个存储属性名称和值对的表。您在这里对数据完整性的控制较少,但您当然可以将属性名称限制为某些字符串。我在这里写过这个:

What is so bad about EAV, anyway?

【讨论】:

我会在诸如“CanDevelopWeb”之类的东西上使用 EAV,而对诸如姓名/家庭/地址之类的东西使用“更传统的结构”。 @jcolebrand 是的,我并不是要暗示 all 列应该是 EAV,只是那些并非所有员工类型都通用的列。 抱歉,我是在附议您对此的看法。【参考方案2】:

您正在描述3 possible strategies 中的一个(“每个表的类”),用于实现类别(也称为继承、泛化、子类)层次结构。

PK 从父表到子表的正确“传播”自然是由它们之间的直接外键强制执行的,但确保子行的存在性和排他性是另一回事。它可以完成(如上面的链接中所述),但增加的复杂性可能不值得,我通常建议在应用程序级别处理它。

【讨论】:

【参考方案3】:
    我会在EmployeeTable 中添加一个名为EmployeeId 的字段 我会摆脱TypeBusinessManager 表和SoftwareDeveloper 为例,我将添加EmployeeId 从这里,您可以继续创建从BusinessManagerSoftwareDeveloper 表到Employee 的外键

【讨论】:

【参考方案4】:

进一步扩展核心表的一种方法是根据标识列创建代理键。这将为每个员工创建一个唯一的员工 ID(这也将帮助您区分同名的员工)。

外键可保持您的参照完整性。您不必像其他人提到的那样需要 EmployeeTypeId,因为您可以过滤 SoftwareDeveloper 或 BusinessManagers 表中的存在。该列将改为充当缓存数据点,以便于查询。

您必须在下面的示例代码中填写类型并重命名外键。

create table EmployeeType(
    EmployeeTypeId
,   EmployeeTypeName
,   constraint PK_EmployeeType primary key (EmployeeTypeId)
)

create table Employees(
    EmployeeId int identity(1,1)
,   Name
,   Family
,   EmployeeTypeId
,   constraint PK_Employees primary key (EmployeeId)
,   constraint FK_blahblah foreign key (EmployeeTypeId) references EmployeeType(EmployeeTypeId)
)

create table SoftwareDeveloper(
    EmployeeId
,   Language
,   Technology
,   CanDevelopWeb
,   constraint FK_blahblah foreign key (EmployeeId) references Employees(EmployeeId)
)

create table BusinessManagers(
    EmployeeId
,   FieldOfExpertise
,   MaximumContractValue
,   BonusRate
,   constraint FK_blahblah foreign key (EmployeeId) references Employees(EmployeeId)
)

【讨论】:

【参考方案5】:

现有的 SQL 引擎没有解决方案可以让您在这种情况下轻松自如。

在“实体子类型化”一章的“数据库管理中的实际问题”中对您的问题进行了相当广泛的讨论。值得称赞的读物,不仅适用于这一章。

从逻辑设计的角度来看,正确的解决方案与您的解决方案类似,但针对核心表中的“类型”列。您不需要那个,因为您可以从员工出现的非核心表中派生“类型”。

您需要查看的是业务规则,也就是数据约束,它将确保数据的整体完整性(也就是一致性)(当然,这些规则是否真正适用是您的业务用户,而不是我,应该告诉你):

每个指定的员工必须有一份工作,因此在某处有一些工作细节。 iow :(1) 没有任何工作详细信息的指定员工和 (2) 没有 >1 工作详细信息的指定员工。

(3) 所有工作详细信息必须是指定员工的。

其中,如果您使用 SQL 引擎,则 (3) 是唯一可以以声明方式实现的。从非核心表到核心表只是一个常规的 FK。

(1) 和 (2) 可以在标准 SQL 中以声明方式定义,使用 CREATE ASSERTION 或 CHECK CONSTRAINT 涉及对其他表的引用,而不是定义 CHECK CONSTRAINT 的表,但这些构造都不受任何支持我知道的 SQL 引擎。

关于为什么 [包括] 'type' 列是一个相当糟糕的选择的另一件事:它改变了必须制定约束 (3) 的方式。例如,您不能再说“所有业务经理都必须是雇员”,而是必须说“所有业务经理都是类型为 的雇员”。 Iow,您的核心表的“常规 FK”现在已成为对您的核心表上的 VIEW 的引用,您可能想要声明为,例如,

CREATE TABLE BUSMANS ... REFERENCES (SELECT ... FROM CORE WHERE TYPE='BM');

创建视图 BM AS (SELECT ... FROM CORE WHERE TYPE='BM'); 创建表 BUSMANS ... 参考 BM;

又一次 SQL 不允许你做的事情。

【讨论】:

【参考方案6】:

您可以使用同一个表中的所有字段,但是您需要一个名为Employee_Type 的额外表(例如),并且您必须在此处输入Developer, Business Manager, ...当然还有一个唯一的ID。所以你的关系将是employee_type_id in Employee table

使用 php 或 ASP,您可以根据下拉菜单中的 employee_type_id(或文本)来控制要显示的字段。

【讨论】:

【参考方案7】:

你在正确的轨道上。您可以设置从一般人员表到每个专用表的 PK/FK 关系。您应该将 personID 添加到用于关系的所有表中,因为您不想在 name 上建立关系,因为它不能是 PK,因为它不是唯一的。名称也会更改,对于 FK 关系来说,它们是一个非常糟糕的选择,因为名称更改可能会导致许多记录需要更改。使用单独的表而不是一个表很重要,因为其中一些是一对多的关系。实例的开发人员可能有许多不同的技术,并且这种东西永远不应该存储在逗号分隔的列表中。

您还可以设置触发器以强制只有在主记录具有特定 personType 时才能将记录添加到专业表中。但是,请小心这样做,因为您会有人随着时间的推移而改变角色。您是否想丢失当他被提升为经理时,他是一名开发人员时所知道的事情的历史。然后,如果他决定退出开发(经常发生),您将不得不重新创建他的旧记录。

【讨论】:

以上是关于如何在 SQL 表中实现这种数据结构的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL Server 中实现一对零或一关系

在GridControl表格控件中实现多层级主从表数据的展示

Oracle 中实现随机抽取数据

如何在SQL中实现区分大小写的查询

在 pl/sql 过程中实现水平碎片表

我应该如何在 SQL Server 2005 中实现“自动编号”字段?