為具有多個多對多關係的影片遊戲業務領域設計數據庫
我對數據庫設計比較陌生,因此我決定製作自己的假設數據庫進行練習。但是,我在建模和規範化它時遇到了麻煩,因為我認為存在許多多對多 (M:N) 關係。
一般場景描述
該數據庫旨在保留有關從事塞爾達系列工作的各種人員的數據。我想跟踪可以玩遊戲的**控制台、參與遊戲開發的員工、員工的工作(許多員工在多個遊戲中從事不同的工作)等。
商業規則
- 多個員工可以在多個遊戲上工作。
- 多個遊戲可以在同一個控制台上。
- 多個控制台可以是同一個遊戲的平台。
- 多個員工可以擁有相同的Job。
- 一個Employee可以有多個Jobs。
- 一個遊戲可以有多個員工。
- 一個遊戲在它的開發中可以有多種類型的工作
- 多個遊戲可以附加相同類型的作業。
- 一個控制台可以有多個人在上面工作。
- 一個人可以在多個控制台上工作。
屬性名稱和範例值
- Employee Name,可以分為First和Last(例如 “John” 和 “Doe” )
- 遊戲名稱(例如“時之笛”)
- 職位名稱(例如“關卡設計”、“總監”、“沉著”、“關卡設計師”、“程序員”、“本地化”等)。
- 控制台名稱(例如“Game Boy Advance”)
問題
到目前為止,似乎無論我設計什麼,在所有感興趣的實體類型之間都存在數據冗餘和 M:N 關係。但是我覺得數據庫設計者一定會經常遇到這種問題,所以一定有解決的辦法。
注意:我很能夠找到填充表格的數據,問題是將其組織到一個帶有標準化表格的數據庫中。
是的,多對多(簡稱 M:N)關聯或關係的辨識是數據庫從業人員在佈置概念模式時經常面臨的情況。所述基數比的關聯出現在性質非常不同的商業環境中,並且當通過例如SQL-DDL安排在邏輯級別上適當地表示時,它們不會引入有害的冗餘。
這樣,數據庫建模練習的目標應該是高精度地反映感興趣的業務上下文的相關特徵;因此,如果您正確地辨識出有許多 M:N 關聯,那麼您必須在 (a) 概念模式和 (b) 相應的邏輯級聲明中表達它們,無論它們有多少連接 - 或任何其他——必須解決基數比。
商業規則
您提供了一個上下文相關的問題,並且還澄清了您正在處理的數據庫純粹是假設的,這是很重要的一點,因為我認為像正在考慮的那樣的“現實世界”業務場景會更加廣泛因此,這意味著更複雜的資訊需求。
我決定 (1) 對您提供的業務規則進行一些修改和擴展,以便 (2) 產生更具描述性的概念模式——儘管仍然是假設性的——。以下是我整理的一些配方:
- 第1方是個人或組織
- 一個Party由完全一個PartyType分類
- PartyType對零個或多個締約方**進行分類
- 一個組織開發零一或多產品
- 產品是系統或遊戲_
- 一個產品被精確地分類為一個ProductType
- 一個System由一個SystemType編目
- 可以通過一對多系統玩**遊戲
- 系統用於玩一對多遊戲
- 遊戲按零一或多類型分類
- 流派對零一或多遊戲進行分類
- 一個產品產生一對多的工作
- 一項工作由零個或多個人完成*,他們扮演協作者的角色*
- 一個人是零一或多工作中的**協作者
1 Party是法律上下文中使用的術語,指的是組成單個實體的個人或一組個人,因此該名稱適用於代表人和組織。
IDEF1X 圖
隨後,我創建瞭如圖 1所示的 IDEF1X 2圖表(確保點擊連結以更高解析度查看它),將上面介紹的業務規則(以及其他一些似乎相關的規則)合併到一個圖形設備中:
2 資訊建模集成定義( IDEF1X ) 是一種高度推薦的數據建模技術,於 1993 年 12 月由美國國家標準與技術研究院(NIST)確立為*標準。*它基於 (a) 由關係模型的唯一創始人,即 EF Codd 博士撰寫的早期理論材料;關於 (b) 數據的實體關係視圖,由PP Chen 博士開發;以及 (c) Robert G. Brown 創建的邏輯數據庫設計技術。
如您所見,我僅通過相應的關聯實體類型描述了三個 M:N 關聯,即:
- 合作者
- 系統遊戲
- 遊戲類型
除其他方面外,還有兩種不同的超型-亞型結構,其中:
- Person和Organization是**Party的互斥實體子類型,它們的實體超類型
- Product是System和Game的超類型,它們又是互斥的子類型
如果您不熟悉超類型-子類型關聯,您可能會找到幫助,例如,我對以下問題的回答:
- “模擬每個音樂藝術家是一個團體或一個獨奏者的場景”
- “如何對可以具有不同屬性集的實體類型進行建模?”
- “為資金轉移業務開發數據庫, (a) 個人和組織可以 (b) 發送和接收資金”
- “為多種使用者類型及其聯繫資訊建模數據庫結構”
說明性邏輯 SQL-DDL 佈局
接下來,我們必須確保在邏輯層面:
- 每個實體類型都由一個單獨的基表表示
- 適用實體類型的每個單一屬性由特定列表示
- 為每一列固定了一個精確的數據類型,以確保它包含的所有值都屬於一個特定且定義明確的集合,無論是 INT、DATETIME、CHAR 等(當然,在使用Firebird或PostgreSQL時,您可能希望使用更強大的 DOMAIN)
- (以聲明方式)配置多個約束,以保證所有表中保留的行形式的斷言符合在概念級別確定的業務規則
所以我根據之前展示的IDEF1X圖聲明了以下DDL安排:
CREATE TABLE PartyType ( -- Stands for an independent entity type. PartyTypeCode CHAR(1) NOT NULL, -- To retain 'P' or 'O'. Name CHAR(30) NOT NULL, -- To keep 'Person' or 'Organization'. -- CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode) ); CREATE TABLE Party ( -- Represents an entity supertype. PartyId INT NOT NULL, PartyTypeCode CHAR(1) NOT NULL, -- To hold the value that indicates the type of the row denoting the complementary subtype occurrence: either 'P' for 'Person' or 'O' for 'Organization'. CreatedDateTime TIMESTAMP NOT NULL, -- CONSTRAINT Party_PK PRIMARY KEY (PartyId), CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode) REFERENCES PartyType (PartyTypeCode) ); CREATE TABLE Person ( -- Denotes an entity subtype. PersonId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY. FirstName CHAR(30) NOT NULL, LastName CHAR(30) NOT NULL, GenderCode CHAR(3) NOT NULL, BirthDate DATE NOT NULL, -- CONSTRAINT Person_PK PRIMARY KEY (PersonId), CONSTRAINT Person_AK UNIQUE (FirstName, LastName, GenderCode, BirthDate), -- Composite ALTERNATE KEY. CONSTRAINT PersonToParty_FK FOREIGN KEY (PersonId) REFERENCES Party (PartyId) ); CREATE TABLE Organization ( -- Stands for an entity subtype. OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY. Name CHAR(30) NOT NULL, FoundingDate DATE NOT NULL, -- CONSTRAINT Organization_PK PRIMARY KEY (OrganizationId), CONSTRAINT Organization_AK UNIQUE (Name), -- Single-column ALTERNATE KEY. CONSTRAINT OrganizationToParty_FK FOREIGN KEY (OrganizationId) REFERENCES Party (PartyId) ); CREATE TABLE ProductType ( -- Represents an independent entity type. ProductTypeCode CHAR(1) NOT NULL, -- To enclose the values 'S' and 'G' in the corresponding rows. Name CHAR(30) NOT NULL, -- To comprise the values 'System' and 'Person' in the respective rows. -- CONSTRAINT ProductType_PK PRIMARY KEY (ProductTypeCode) ); CREATE TABLE Product ( -- Denotes an entity supertype. OrganizationId INT NOT NULL, ProductNumber INT NOT NULL, ProductTypeCode CHAR(1) NOT NULL, -- To keep the value that indicates the type of the row denoting the complementary subtype occurrence: either 'S' for 'System' or 'G' for 'Game'. CreatedDateTime DATETIME NOT NULL, -- CONSTRAINT Product_PK PRIMARY KEY (OrganizationId, ProductNumber), -- Composite PRIMARY KEY. CONSTRAINT ProductToOrganization_FK FOREIGN KEY (OrganizationId) REFERENCES Organization (OrganizationId), CONSTRAINT ProductToProductType_FK FOREIGN KEY (ProductTypeCode) REFERENCES ProductType (ProductTypeCode) ); CREATE TABLE SystemType ( -- Stands for an independent entity type. SystemTypeCode CHAR(1) NOT NULL, Name CHAR(30) NOT NULL, -- CONSTRAINT SystemType_PK PRIMARY KEY (SystemTypeCode) ); CREATE TABLE MySystem ( -- Represents a dependent entity type. OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY. SystemNumber INT NOT NULL, SystemTypeCode CHAR(1) NOT NULL, ParticularColumn CHAR(30) NOT NULL, -- CONSTRAINT System_PK PRIMARY KEY (OrganizationId, SystemNumber), CONSTRAINT SystemToProduct_FK FOREIGN KEY (OrganizationId, SystemNumber) REFERENCES Product (OrganizationId, ProductNumber), CONSTRAINT SystemToSystemType_FK FOREIGN KEY (SystemTypeCode) REFERENCES SystemType (SystemTypeCode) ); CREATE TABLE Game ( -- Denotes an entity subtype. OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY. GameNumber INT NOT NULL, SpecificColumn CHAR(30) NOT NULL, -- CONSTRAINT Game_PK PRIMARY KEY (OrganizationId, GameNumber), CONSTRAINT GameToProduct_FK FOREIGN KEY (OrganizationId, GameNumber) REFERENCES Product (OrganizationId, ProductNumber) ); CREATE TABLE Genre ( -- Stands for an independent entity type. GenreNumber INT NOT NULL, Name CHAR(30) NOT NULL, Description CHAR(90) NOT NULL, -- CONSTRAINT Genre_PK PRIMARY KEY (GenreNumber), CONSTRAINT Genre_AK1 UNIQUE (Name), CONSTRAINT Genre_AK2 UNIQUE (Description) ); CREATE TABLE SystemGame ( -- Represents an associative entity type or M:N association. SystemOrganizationId INT NOT NULL, SystemNumber INT NOT NULL, GameOrganizationId INT NOT NULL, GameNumber INT NOT NULL, CreatedDateTime DATETIME NOT NULL, -- CONSTRAINT SystemGame_PK PRIMARY KEY (SystemOrganizationId, SystemNumber, GameOrganizationId, GameNumber), -- Composite PRIMARY KEY. CONSTRAINT SystemGameToSystem_FK FOREIGN KEY (SystemOrganizationId, SystemNumber) -- Multi-column FOREIGN KEY. REFERENCES MySystem (OrganizationId, SystemNumber), CONSTRAINT SystemGameToGame_FK FOREIGN KEY (SystemOrganizationId, GameNumber) -- Multi-column FOREIGN KEY. REFERENCES Game (OrganizationId, GameNumber) ); CREATE TABLE GameGenre ( -- Denotes an associative entity type or M:N association. GameOrganizationId INT NOT NULL, GameNumber INT NOT NULL, GenreNumber INT NOT NULL, CreatedDateTime DATETIME NOT NULL, -- CONSTRAINT GameGenre_PK PRIMARY KEY (GameOrganizationId, GameNumber, GenreNumber), -- Composite PRIMARY KEY. CONSTRAINT GameGenreToGame_FK FOREIGN KEY (GameOrganizationId, GameNumber) REFERENCES Game (OrganizationId, GameNumber), -- Multi-column FOREIGN KEY. CONSTRAINT GameGenreToGenre_FK FOREIGN KEY (GenreNumber) REFERENCES Genre (GenreNumber) ); CREATE TABLE Job ( -- Stands for an associative entity type or M:N association. OrganizationId INT NOT NULL, ProductNumber INT NOT NULL, JobNumber INT NOT NULL, Title CHAR(30) NOT NULL, CreatedDateTime DATETIME NOT NULL, -- CONSTRAINT Job_PK PRIMARY KEY (OrganizationId, ProductNumber, JobNumber), -- Composite PRIMARY KEY. CONSTRAINT Job_AK UNIQUE (Title), -- Single-column ALTERNATE KEY. CONSTRAINT JobToProduct_FK FOREIGN KEY (OrganizationId, ProductNumber) -- Multi-column FOREIGN KEY. REFERENCES Product (OrganizationId, ProductNumber) ); CREATE TABLE Collaborator ( -- Represents an associative entity type or M:N association. CollaboratorId INT NOT NULL, OrganizationId INT NOT NULL, ProductNumber INT NOT NULL, JobNumber INT NOT NULL, AssignedDateTime DATETIME NOT NULL, -- CONSTRAINT Collaborator_PK PRIMARY KEY (CollaboratorId, OrganizationId, ProductNumber, JobNumber), -- Composite PRIMARY KEY. CONSTRAINT CollaboratorToPerson_FK FOREIGN KEY (CollaboratorId) REFERENCES Person (PersonId), CONSTRAINT CollaboratorToJob_FK FOREIGN KEY (OrganizationId, ProductNumber, JobNumber) -- Multi-column FOREIGN KEY. REFERENCES Job (OrganizationId, ProductNumber, JobNumber) );
值得強調的是,在多個表中聲明了複合PRIMARY KEY 約束,它們代表概念實體類型之間發生的連接層次結構,這種排列對於數據檢索非常有益,例如,表達 SELECT包含 JOIN 子句以獲取派生表的操作。
是的,(i) 每個 M:N 關聯和 (ii) 每個關聯實體類型都由 (iii) 邏輯 DDL 結構中的對應表表示,因此要特別注意 PRIMARY 和 FOREIGN KEY 約束(以及我作為評論留下的註釋)代表這些概念元素的表格,因為它們有助於確保相關行之間的連接符合適用的基數比。
EF Codd 博士從關係範式的起源就介紹了複合鍵的使用,正如他在 1970 年題為“大型共享數據庫的關係模型”的開創性論文中所包含的範例所證明的那樣處理概念 M:N 關聯的最優雅的方法)。
我安裝了 一個 db<>fiddle和一個 SQL Fiddle,它們都在 Microsoft SQL Server 2014 上執行,以便可以“實際”測試該結構。
正常化
規範化是一個邏輯級的過程,基本上說:
- 通過第一範式消除非原子列,以便使用數據子語言(例如 SQL)更容易處理數據操作和限制。
- 通過連續的範式消除特定表的列之間不需要的依賴關係,以避免**更新異常。
自然地,必須考慮所討論的表格和列所承載的含義。
我喜歡將規範化視為一種建立在科學基礎上的測試,一旦設計師描繪了一個穩定的邏輯層次安排,就可以將其應用於相關元素,以確定其項目是否符合每一種正常形式。然後,如果需要,設計者會採取適當的糾正措施。
冗餘
在關係模型中,雖然列中包含的重複值不僅是可以接受的而且是可以接受的,但重複行是被禁止的。就此而言,據我所知,在之前公開的邏輯佈局中包含的所有表中都防止了重複行和其他類型的有害冗餘,也許您想澄清您在這方面的擔憂。
無論如何,您當然可以(a)通過正常形式評估您自己的結構,以確定它是否滿足要求,並(b)在必要時對其進行修改。
相關資源
- 在這一系列文章中,我提出了一些關於可以將兩種不同實體類型的實例相互關聯的簡單 M:N 關聯的思考。
- 在另一篇文章中,我提出了一種處理“材料清單”或“零件爆炸”構造的方法,其中我描述瞭如何連接同一類型實體的不同實例。
三元關聯
您通過評論提出了另一個重要方面(發佈在現已刪除的答案中):
每次我嘗試製作一座橋時,該橋中的元素也有一個多對多,我的印像是不允許或至少不鼓勵。
這種情況似乎表明您的擔憂之一與概念三元關聯有關。基本上,當存在(1)涉及(2)其他兩個關係的關係,即“關係之間的關係”時,就會產生這種關聯——這也是一種典型的情況,因為關係本身就是一個實體——。
如果管理得當,這些安排也不會帶來有害的裁員。而且,是的,如果在某個案例中,您確定此類關係在“現實世界”實體類型中出現,您必須 (i) 建模並 (ii) 在邏輯級別準確地聲明它們。
- 這是一個問題和答案,我們分析了一個關於調查的話語領域,其中包括一個三元關聯的例子。
- 在這個非常好的答案中,@Ypercube為有趣的菱形關係提供了一個圖表和相應的 DDL 結構,這與此類場景非常相似。