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的旧问题的答案.作为一个老问题,没有对话.我需要实现这个例子,我很好奇这个架构的后果.
以下是上一个问题/答案: 良好的数据库表设计:一个表混合不同的实体或每个实体的单独表
我认为休·达文(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 中保存:)