SQL体系结构:这是一个合理的情况,只有一个表存储多个实体类型?(使用自我加入)

Chr*_*gna 5 sql database-design data-modeling

我很少遇到这样的情况:多个实体类型的单个表似乎比每个实体类型一个表好.这是一个对我有意义的例子,但在学术上它似乎是错误的.

问题:我可以这样做并且仍然具有"声音"架构吗?

示例如下

假设有两种实体类型,一个公司和一个人.公司通常由一个人拥有,但有时另一家公司拥有公司.

坚持这一想法并加入其中,让我们说每个公司都有一个附属于它的注册代理人,负责公司的合法创建.进一步说明,注册代理人可以是个人或公司.

如果您认为公司[子女]的所有者[父母]可以是个人或公司,您可能会开始看到保持第三种正常形式并避免冗余的挑战.

与我的例子相反,如果只有人可以拥有公司,则所有权链接表非常传统,具有列:OwnershipID(有点不必要),CorporateID,PersonID.

相反,您需要以下内容:OwnershipID,CorporationID,OwnerID,OwnerEntityType(corp或person).不要误会我的意思,你可以做到这一点,但至少可以说它不会很有趣.

继续我给出的示例,您需要为每个公司分配代理.通常,代理人是所有者(一个人)之一.在这种情况下,您确实想要链接回该人的一条记录.您不希望将此人的记录作为所有者,然后再将其作为代理记录(在代理表中).这将是多余的.

与该"问题"类似,注册代理人也可以是公司,例如律师事务所,注册会计师或商业申请公司,以列举一些典型的例子.就像代理人一样,代理公司真的不应该作为代理人实体获得自己的记录.相反,它需要链接回公司表中的公司存在.[除了我最终说没有公司实体表]

就像链接表将每个公司与其所有类型,个人或公司的所有者相匹配一样,您可以拥有一个代理链接表:AgentRepresentationID,CorporationID,AgentID,AgentType ......但同样,它会很难看( IMO)当你必须把相关的代理 - 一些来自Person表,一些来自公司表.

这就是为什么我说,在这种情况下,你可以看到中性实体类型如何有利.它会是这样的:

表:EntityAll
键列:EntityID,EntityType(或EntityTypeID,如果您坚持,链接以获取描述),EntityName(有关于名称和不同类型的问题...关于此帖子的主题)

链接表:CorporationOwnership
Key Columns:OwnershipID(再次,我的评论,这是一种不必要的),ChildEntityID(被拥有的实体;为了清楚起见,命名为"Child",我不会将其命名为)ParentEntityID(父实体)

链接表:AgentRepresentation
键列:AgentRepresentationID(...我不会说出来),CorporationEntityID(正在表示的公司实体),AgentEntityID(来自Entity表,等于此处代理的记录)

虽然您对我的架构可能没问题,但您应该对链接表中的列命名感到困扰.这让我很烦.通常,这些表中的第二个和第三个列名称与每个实体的相应表中的JOIN列完全匹配(哈哈,但是每个实体都没有相应的表,因此您不能使链接表列名匹配源列名称,因为它们是相同的列).从技术上讲,这无关紧要,但它会破坏你的命名约定,这些约定很重要,但还不足以做到这一点.

如果我还没有把它推回家,那么你将如何把它拉到一起.您可以自己加入EntityAll表以获得所需内容.

列出所有军团及其所有者(在T-SQL中):

SELECT Corp.EntityName as CorpName, Owner.EntityName as OwnerName
FROM EntityAll as Corp
JOIN CorporationOwnership as Link on (Corp.EntityID = Link.ChildEntityID)
JOIN EntityAll as Owner on (Link.ParentEntityID = Owner.EntityID)
Run Code Online (Sandbox Code Playgroud)

因此,您可以执行相同的操作来返回代理,而不是所有者.

我意识到这不是我们如何训练构建表格,但我非常强烈地认为我的解决方案可以消除冗余数据并使代码,管理和读取变得更加容易.

PS我最近提供了这个例子作为SO的旧问题的答案.作为一个老问题,没有对话.我需要实现这个例子,我很好奇这个架构的后果.

以下是上一个问题/答案: 良好的数据库表设计:一个表混合不同的实体或每个实体的单独表

one*_*hen 4

我认为休·达文(Hugh Darwen)创造了术语“分布式键”和“分布式外键”,其中单个引用的键值恰好存在于多个引用相关变量(表)之一中;这将需要“多重赋值”的相关概念,以便原子地插入到被引用和引用的相关变量中。

虽然理论上这可以在 SQL-92 中使用可延迟模式级别ASSERTION(或者可能CHECK支持子查询的约束)来实现,但这是一个相当笨重的过程,是过程性的(而不是基于集合的),并且没有一个 SQL 产品可以做到这一点。曾经支持这个功能(或者我怀疑永远会支持)。

对于可用的 SQL 产品,我们能做的最好的事情就是使用对引用表进行约束的(entity_ID, entity_type)复合键,以确保不存在多个引用键值(请注意,这与“恰好一个引用键值”不同)例如CHECKentity_type

CREATE TABLE LegalPersons
(
 person_ID INTEGER IDENTITY NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type IN ('Company', 'Natural Person')), 
 UNIQUE (person_type, person_ID)
);

CREATE TABLE Companies
(
 person_ID INTEGER NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type = 'Company'), 
 FOREIGN KEY (person_type, person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 companies_house_registered_number VARCHAR(8) NOT NULL UNIQUE
 -- other company columns and constraints here
);

CREATE TABLE NaturalPersons
(
 person_ID INTEGER NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type = 'Natural Person'), 
 FOREIGN KEY (person_type, person_ID)
    REFERENCES LegalPersons (person_type, person_ID) 
 -- natural person columns and constraints here
);
Run Code Online (Sandbox Code Playgroud)

这种超类-子类模式在 SQL 中非常常见。

理想情况下,表名称应反映整个集合的性质。你们可能需要超越其他集合名称的组合来思考;也许询问特定业务领域的专家,例如会计师可能使用术语“工资单”而不是“员工薪水”。

另一个理想的情况是列的名称在整个模式中保持相同,但使用子类化方法时,您经常需要限定它们(这让我很困扰!)例如

CREATE TABLE CompanyAgents
(
 company_person_ID INTEGER NOT NULL UNIQUE, 
 company_person_type VARCHAR(14) NOT NULL
    CHECK (company_person_type = 'Company'), 
 FOREIGN KEY (company_person_type, company_person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 agent_person_ID INTEGER NOT NULL, 
 agent_person_type VARCHAR(14) NOT NULL, 
 FOREIGN KEY (agent_person_type, agent_person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 CHECK (company_person_ID <> agent_person_ID)
);
Run Code Online (Sandbox Code Playgroud)

注意我会使用单列键,agent_person_ID例如

 agent_person_ID INTEGER NOT NULL
    REFERENCES LegalPersons (person_ID)
Run Code Online (Sandbox Code Playgroud)

因为实体类型没有限制。原则上,我觉得为整个模式中的所有引用保留两列复合键会更好,而且我发现在实践中我经常不需要现在的实体类型,因此这个 SQL DDL 在JOINSQL DML 中保存:)