如何避免 3 個表之間的循環依賴(循環引用)?
我有 3 張桌子:
- 人們
- 郵政
- 喜歡
當我設計 ER 模型時,它具有循環依賴:
1:N 人 --------< 發表 1:N 發表 ----------< 贊 1:N 人 --------< 喜歡
邏輯是:
- 1個人可以有很多文章。
- 1 個文章有很多贊。
- 1 人可以點贊很多文章(創建的人不能點贊自己的文章)。
我怎樣才能消除這種循環設計?還是我的數據庫設計錯誤?
商業規則
讓我們對您提出的業務規則進行一些改寫:
- A
Person
創建零一或多Posts
。- A
Post
接收零一或多Likes
。- A
Person
表現出零一或多Likes
,每一個都與一個特定Post
的 相關。邏輯模型
然後,從這樣一組斷言中,我導出了兩個邏輯級別**IDEF1X$$ 1 $$**數據模型如圖 1所示。
選項 A
正如您在選項 A 模型中看到的那樣,
PersonId
**遷移$$ 2 $$fromPerson
toPost
作為外鍵 (FK),但它接收角色名稱$$ 3 $$**的AuthorId
,並且該屬性與 一起構成實體類型PostNumber
的主鍵(PK) 。Post
我假設 a
Like
只能與特定的 關聯存在Post
,因此我設置了一個Like
包含三個不同屬性的 PKPostAuthorId
:PostNumber
和LikerId
。PostAuthorId
和的組合PostNumber
是正確引用Post
PK 的 FK。LikerId
反過來,它是一個與 建立適當關聯的 FKPerson.PersonId
。借助此結構,您可以確保一個確定的人只能
Like
對同一Post
實例顯示一次事件。防止文章作者喜歡他自己的文章的方法
由於您不希望一個人喜歡他/她撰寫的文章,因此一旦在實施階段,您應該建立一個方法,將 的值
Like.PostAuthorId
與Like.LikerId
每次 INSERT 嘗試中的值進行比較。如果所述值匹配,**(a)您拒絕插入,如果它們不匹配(b)**您讓該過程繼續。為了在您的數據庫中完成此任務,您可以使用:
- 一個CHECK CONSTRAINT但是,當然,這個方法不包括 MySQL,因為到目前為止它還沒有在這個平台上實現,你可以在這里和這裡看到。
- ACID Transaction中的程式碼行。
- TRIGGER中的程式碼行,可以返回指示違反規則嘗試的自定義消息。
選項 B
如果作者不是以主要方式辨識您的業務領域中的文章的屬性,您可以使用類似於選項 B 中描述的結構。
這種方法還確保文章只能被同一個人點贊一次。
筆記
資訊建模集成定義 ( IDEF1X ) 是一種高度推薦的數據建模技術,於1993 年 12 月被美國國家標準與技術研究院 ( NIST )定義為標準。
IDEF1X 將鍵遷移定義為“將父實體或通用實體的主鍵作為外鍵放置在其子實體或類別實體中的建模過程”。
3.角色名稱是分配給外鍵屬性的一種表示,以便在其相應實體類型的上下文中表達該屬性的含義。自 1970 年以來, EF Codd 博士在其題為“大型共享數據庫的數據關係模型”的開創性論文中推薦了角色命名。就其本身而言,IDEF1X——保持對關係實踐的忠誠——也提倡這一程序。
我在這裡看不到任何循環。這些實體之間存在人員和職位以及兩種獨立的關係。我認為喜歡是其中一種關係的實現。
- 一個人可以寫很多文章,一個文章是一個人寫的:
1:n
- 一個人可以喜歡很多文章,一個文章可以被很多人喜歡:
n:m
n:m 關係可以用另一個關係來實現:
likes
。基本實現
PostgreSQL中的基本實現可能如下所示:
CREATE TABLE person ( person_id serial PRIMARY KEY , person text NOT NULL ); CREATE TABLE post ( post_id serial PRIMARY KEY , author_id int NOT NULL -- cannot be anonymous REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE -- 1:n relationship , post text NOT NULL ); CREATE TABLE likes ( -- n:m relationship person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE , post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE , PRIMARY KEY (post_id, person_id) );
文章必須有作者(
NOT NULL
),而任何喜歡的存在都是可選的。但是,對於現有的喜歡,post
並且person
必須都被引用 - 由隱式PRIMARY KEY
生成兩個列的 強制執行。NOT NULL
所以匿名點贊是不可能的。關於 n:m 實現:
防止自戀
你寫了:
(創建的人不能喜歡他自己的文章)。
在上面的實現中還沒有強制執行。您可以使用觸發器。
或者這些更快/更可靠的解決方案之一:
堅如磐石的成本
如果它需要堅如磐石,請將 FK 從 擴展
likes
到post
以包含author_id
冗餘。CHECK
然後你可以用一個簡單的約束來排除亂倫:CREATE TABLE likes ( person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE , post_id int , **author_id int NOT NULL** , CONSTRAINT likes_pkey PRIMARY KEY (post_id, person_id) , **CONSTRAINT likes_post_fkey FOREIGN KEY (author_id, post_id) REFERENCES post(author_id, post_id) ON UPDATE CASCADE ON DELETE CASCADE , CONSTRAINT no_self_like CHECK (person_id <> author_id)** );
這需要在 中的其他冗餘
UNIQUE
約束post
:ALTER TABLE post ADD CONSTRAINT post_for_fk_uni UNIQUE (author_id, post_id);
我
author_id
首先提供一個有用的索引,同時在它上面。有關的:
有
CHECK
約束條件更便宜建立在上面的*“基本實現”*之上。
CHECK
約束是不可變的。引用另一個表從來都不是一成不變的,我們在這裡有點濫用這個概念。聲明約束**NOT VALID
**以正確反映這一點。看:在
CHECK
這種特殊情況下,約束似乎是合理的,因為文章的作者似乎是一個永遠不會改變的屬性。確保不允許更新該欄位。我們偽造一個
IMMUTABLE
函式:CREATE OR REPLACE FUNCTION f_author_id_of_post(_post_id int) RETURNS int LANGUAGE sql IMMUTABLE AS 'SELECT p.author_id FROM public.post p WHERE p.post_id = $1';
將“public”替換為表的實際架構。在約束
中使用此函式:
CHECK
ALTER TABLE likes ADD CONSTRAINT no_self_like_chk CHECK (f_author_id_of_post(post_id) <> person_id) NOT VALID;