Postgresql

具有兩種可能的所有者/父類型的實體的數據庫模式?

  • June 14, 2018

我正在使用帶有Sequelize的 PostgreSQL作為我的 ORM。

我有一種類型,User. 第二種類型是Group,它可以通過一個GroupMemberships表關聯任意數量的使用者。Users 也可以擁有任意數量的Groups。

我的第三種類型 ,Playlist可以屬於 aUser或 a group。為這種類型設計模式的最佳方法是什麼,以便它可以擁有一種類型的所有者或任何一種?

我的第一遍創建了兩個關聯,但一次只填充了一個。這可能可行,但看起來很hacky並且使查詢變得困難。

附加資訊

以下是我對 MDCCL 通過評論發布的澄清請求的回應:

(1) 如果一個播放列表屬於給定,可以說這個播放列表與一對多使用者相關,只要他們是該組的**成員,對嗎?

我相信這在技術上是正確的,但是這種一對多的關聯並不明確存在。

(2) 那麼,一個特定的播放列表是否可以同時由一對多的擁有?

不, a 不可能Playlist由 one-to-many 擁有Groups

(3) 特定播放列表是否可以由一對多擁有,同時由不是該組**成員的一對多使用者擁有?

不,因為如(2)中的一對多 from PlaylisttoGroup不應該存在。此外,如果 aPlaylist由 a 擁有,Group則它不屬於 a User,反之亦然。一次只有一個所有者。

(4) 用於唯一標識使用者播放列表的屬性是什麼?

它們每個都有一個代理主鍵 ( id) 和一個自然鍵(雖然不是主鍵)。這些slugGroupPlaylist,和usernameUser

(5) 特定播放列表是否會發生所有者更改?

儘管我不打算將其作為一個功能(至少最初是這樣),但我認為這可能會發生。

(6) Group.SlugPlaylist.Slug屬性是什麼意思?它們的值是否足夠穩定,可以定義為主鍵,還是經常更改?這兩個屬性的值以及User.Username必須是唯一的,對嗎?

這些slugs 是其各自實體的唯一、小寫、連字元版本title。例如,group帶有title“測試組”的 a 將具有slug“測試組”。重複項附加了增量整數。這將隨時改變他們的title變化。我相信這意味著他們不會做出很好的主鍵?是的,slugs並且usernames在各自的表格中是獨一無二的。

如果我正確理解了您的規範,那麼您的場景涉及(除其他重要方面外)超類型 - 子類型結構。

我將在下面舉例說明如何(1)在抽象的概念級別對其進行建模,然後(2)在邏輯級別的DDL 設計中表示它。

商業規則

以下概念公式是您業務環境中最重要的規則之一:

  • 播放列表在特定時間點由一個或一個使用者擁有
  • 播放列表可能由一對多使用者在不同時間點擁有
  • 使用者擁有零個或多個播放**列表
  • A Groups擁有零個一個或多個播放列表
  • 一個由一對多的成員(必須是使用者)組成
  • 使用者可能是零一或多成員
  • 一個由一對多的成員(必須是使用者)組成

由於 (a) UserPlaylist之間以及 (b) GroupPlaylist之間的關聯或關係非常相似,這一事實表明UserGroupParty 1的互斥實體子類型,而後者又是它們的實體超類型 -supertype-子類型集群是經典的資料結構,出現在非常多樣化的概念模式中——。以這種方式,可以斷言兩個新規則:

  • 派對僅由一個PartyType分類
  • 一方使用者_

之前的四個規則必須重新表述為只有三個:

  • 播放列表在特定時間點僅由一方擁有
  • 播放列表可能由一對多在不同時間點擁有
  • 一方擁有零個或多個播放列表

說明性IDEF1X圖

圖 1所示的 IDEF1X 2圖表整合了所有上述業務規則以及其他相關的規則:

圖 1 - 播放列表所有者 IDEF1X 圖

如圖所示,GroupUser被描繪為子類型,它們通過各自的線和與超類型Party的**專有符號連接。

Party.PartyTypeCode屬性代表子類型鑑別**器,即它指示哪種子類型實例必須補充給定的超類型出現。

此外,Party通過OwnerId屬性與Playlist連接,該屬性被描述為指向Party.PartyId的 FOREIGN KEY 。通過這種方式,Party將 (a) Playlist與 (b) Group和 (c) User相互關聯。

因此,由於特定的Party實例是GroupUser,特定的播放列表最多可以與一個子類型出現連結。

說明性邏輯級佈局

之前闡述的 IDEF1X 圖為我提供了創建以下邏輯 SQL-DDL 排列的平台(並且我提供了註釋作為註釋,突出了幾個特別相關的點——例如,約束聲明——):

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business domain.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE PartyType ( -- Represents an independent entity type.
   PartyTypeCode CHAR(1)  NOT NULL,
   Name          CHAR(30) NOT NULL,  
   --
   CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode),
   CONSTRAINT PartyType_AK UNIQUE      (Name)  
);

CREATE TABLE Party ( -- Stands for the supertype.
   PartyId         INT       NOT NULL,
   PartyTypeCode   CHAR(1)   NOT NULL, -- Symbolizes the discriminator.
   CreatedDateTime TIMESTAMP NOT NULL,  
   --
   CONSTRAINT Party_PK            PRIMARY KEY (PartyId),
   CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
       REFERENCES PartyType (PartyTypeCode)
);

CREATE TABLE UserProfile ( -- Denotes one of the subtypes. 
   UserId     INT      NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
   UserName   CHAR(30) NOT NULL,  
   FirstName  CHAR(30) NOT NULL,
   LastName   CHAR(30) NOT NULL,
   GenderCode CHAR(3)  NOT NULL,
   BirthDate  DATE     NOT NULL,
   --
   CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
   CONSTRAINT UserProfile_AK1 UNIQUE ( -- Multi-column ALTERNATE KEY.
       FirstName,
       LastName,
       GenderCode,
       BirthDate
   ),
   CONSTRAINT UserProfile_AK2       UNIQUE (UserName), -- Single-column ALTERNATE KEY.
   CONSTRAINT UserProfileToParty_FK FOREIGN KEY (UserId)
       REFERENCES Party (PartyId)
);

CREATE TABLE MyGroup ( -- Represents the other subtype.
   GroupId INT      NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
   Title   CHAR(30) NOT NULL,
   --
   CONSTRAINT Group_PK        PRIMARY KEY (GroupId),
   CONSTRAINT Group_AK        UNIQUE      (Title), -- ALTERNATE KEY.
   CONSTRAINT GroupToParty_FK FOREIGN KEY (GroupId)
       REFERENCES Party (PartyId)
);

CREATE TABLE Playlist ( -- Stands for an independent entity type.
   PlaylistId      INT       NOT NULL,
   OwnerId         INT       NOT NULL,  
   Title           CHAR(30)  NOT NULL,
   CreatedDateTime TIMESTAMP NOT NULL,  
   --
   CONSTRAINT Playlist_PK     PRIMARY KEY (PlaylistId),
   CONSTRAINT Playlist_AK     UNIQUE      (Title),  -- ALTERNATE KEY.
   CONSTRAINT PartyToParty_FK FOREIGN KEY (OwnerId) -- Establishes the relationship with (a) the supertype and (b) through the subtype with (c) the subtypes.
       REFERENCES Party (PartyId)
);

CREATE TABLE GroupMember ( -- Denotes an associative entity type.
   MemberId       INT       NOT NULL, 
   GroupId        INT       NOT NULL,
   IsOwner        BOOLEAN   NOT NULL,    
   JoinedDateTime TIMESTAMP NOT NULL,
   --        
   CONSTRAINT GroupMember_PK              PRIMARY KEY (MemberId, GroupId), -- Composite PRIMARY KEY.
   CONSTRAINT GroupMemberToUserProfile_FK FOREIGN KEY (MemberId)
       REFERENCES UserProfile (UserId),
   CONSTRAINT GroupMemberToMyGroup_FK     FOREIGN KEY (GroupId)
       REFERENCES MyGroup (GroupId)  
);

當然,您可以進行一項或多項調整,以便在實際數據庫中以所需的精度表示業務上下文的所有特徵。

注意:我已經在這個 db<>fiddle這個 SQL Fiddle上測試了上面的邏輯佈局,它們都在 PostgreSQL 9.6 上“執行”,所以你可以看到它們“在執行”。

蛞蝓

如您所見,我沒有在 DDL 聲明中包含Group.Slug也不Playlist.Slug作為列。之所以如此,是因為,與您的以下解釋一致

這些slugs 是其各自實體的唯一、小寫、連字元版本title。例如,group帶有title“測試組”的 a 將具有slug“測試組”。重複項附加了增量整數。這將隨時改變他們的title變化。我相信這意味著他們不會做出很好的主鍵?是的,slugs並且usernames在各自的表格中是獨一無二的。

可以得出結論,它們的值是可導出的(即,它們必鬚根據對應的Group.TitlePlaylist.Title值來計算或計算,有時與 - 我假設,某種系統生成的 - INTEGER 結合使用),所以我不會聲明所述列在任何基中,因為它們會引入更新異常。

相反,我會生產Slugs

  • 也許,在一個視圖中,(a)包括虛擬列中這些值的派生,(b)可以直接用於進一步的 SELECT 操作——附加 INTEGER 部分可以獲得,例如,通過組合(1)的值帶有 (2) 中間連Playlist.OwnerId字元和 (3) 的值Playlist.Title
  • 或者,通過應用程式碼,模仿之前描述的方法(可能在程序上),一旦相關數據集被檢索並格式化以供最終使用者解釋。

以這種方式,這兩種方法中的任何一種都可以避免“更新同步”機制,如果Slugs它們保留在基表的列中,則該機制應該到位。

完整性和一致性考慮

重要的是要提到 (i)每一 Party行必須始終由 (ii)恰好其中一個代表子類型的表中的相應對應項進行補充,(iii) 必須“符合”Party.PartyTypeCode列中包含的值——表示鑑別器——。

以聲明的方式強制執行這種情況是非常有利的,但是沒有一個主要的 SQL 數據庫管理系統(包括 Postgres)提供了必要的工具來進行這樣的操作;因此,到目前為止,在ACID TRANSACTIONS中編寫過程程式碼是保證在您的數據庫中*始終滿足前面描述的情況的最佳選擇。*其他可能性是訴諸觸發器,但可以這麼說,它們很容易使事情變得不整潔。

可比案例

如果您想建立一些類比,您可能有興趣查看我對題為的(較新)問題的回答

因為討論了可比較的場景。


尾註

1 Party是法律上下文中使用的術語,指的是組成單個實體的**個人或一,因此該名稱適用於代表使用者集團在相關業務環境方面的概念。

2 資訊建模集成定義( IDEF1X ) 是一種高度推薦的數據建模技術,於 1993 年 12 月由美國國家標準與技術研究院(NIST)確立為*標準。*它完全基於 (a) 由關係模型的唯一創始人,即EF Codd 博士撰寫的一些早期理論著作;(b)由PP Chen 博士開發的實體關係視圖;以及 (c) Robert G. Brown 創建的邏輯數據庫設計技術。

引用自:https://dba.stackexchange.com/questions/107919