2012-02-12 105 views
9

作爲回答這樣一個問題:Cardinality in PostgreSQL,基數是使用constraints enfforced。PostgreSQL的:如何實現最小基數?

基數規則定義關係的允許計數 - 一對多,多對多等。使用連接表和一對多使用FOREIGN KEY實現多對多。

但如何能實現一到one_or_many(一到-1 +)的關係。這是一樣的問:我怎樣才能執行最低基數PostgreSQL中?

的實際情況是其中一個需要存儲的地址說(或電話號碼),必須提供(但可以不止一個)由人(比如用戶或客戶)。

編輯:

上面提到的情況是一種特殊情況(具有基數的一個)的一個普遍問題的。一般問題是:如何強制任意數字的基數?

作爲answered by jug一個非空外鍵引用可以被用作一個替代解決方法,如果最小基數是一個。它還將提供一個附加功能來選擇許多默認設置。

但考慮Cricket及其球員之間關係的另一種情況。每支球隊都必須有11名球員最低資格作爲一個團隊。這裏最小基數是11(11)。

與之相似,一個當然學生在學校,每個學生必須報名參加AT-至少5個課程,每個課程必須有10名學生最低之間的關係。

+1

這兩個添加的示例顯示相同的質量。你如何從頭開始建立一個板球隊?一個學生如何參加他的第一門課程?少於5門課程的學生必須被允許存在,至少暫時存在。所以這是**不是一個使用約束的好地方。爲此目的使用查詢,函數或觸發器。 – 2012-02-14 00:03:32

回答

3

有沒有辦法使用CHECK約束來指定這個,所以我認爲最好的辦法是觸發:

http://www.postgresql.org/docs/9.1/static/sql-createtrigger.html
http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html

你最終喜歡的東西(我沒有測試了它或任何東西):

CREATE TRIGGER at_least_one before INSERT, UPDATE, DELETE ON the_one_table FOR EACH ROW EXECUTE PROCEDURE check_at_least_one(); 

CREATE OR REPLACE FUNCTION check_at_least_one() RETURNS trigger AS $$ 
    BEGIN 
    nmany := select count(*) from the_many_table where the_many_table.the_one_id=NEW.id; 
    IF nmany > 0 THEN 
     RETURN NEW; 
    END IF; 
    RETURN NULL; 
END; 
+0

初始INSERT是如何完成的?由於必須首先插入ONE表(以確保引用的外鍵存在),因此可以插入引用ONE表的MANY表中的第一行。但是你的觸發器會禁止這個。 – 2012-02-12 15:36:37

+0

回答自己:通過使用兩個(!!)'CONSTRAINT'觸發器,它們是'INITIALLY DEFERRED'。這些觸發器不應該返回NULL,而是使用'RAISE EXCEPTION'。第一個觸發器監視「一個」表,另一個觸發「多個」表(以在第一個事務之後捕獲刪除)。 – 2012-02-13 16:57:20

1

如果你在一個表中的地址不會忽略,你可以定義列「defau lt_address」(表客戶),其對必須提供的地址非空外鍵引用。

如果你有一個表運送,通過引用一個人,一個地址和可能的訂單(-Item)提供可選的許多一對多的關係,那麼你可以使用COALESCE改寫爲地址的NULL你得到在外部聯接之間(客戶內部聯接訂單)和shipping_addresses(一個視圖接合運送地址)。但是,爲了防止可能不同數量的地址的非空組件的問題,斯特凡Faroult建議他(強烈推薦!)書SQL的藝術用「隱藏排序關鍵字」技術(假設customers_with_default_address是使用「DEFAULT_ADDRESS」加入客戶地址一個觀點:

select * 
    from (select 1 as sortkey, 
       line_1, 
       line_2, 
       city, 
       state, 
       postal_code, 
       country 
     from shipping_addresses 
      where customer_id = ? 
     union 
     select 2 as sortkey, 
       line_1, 
       line_2, 
       city, 
       state, 
       postal_code, 
       country 
     from customers_with_default_address 
      where customer_id = ? 
     order by 1) actual_shipping_address 
     limit 1 
+0

啊,_default_address_列的想法是實用的,只有在想要強制執行最小基數(1)時纔有意義。作爲一個實際情況,作爲問題的一部分,我所提出的只是衆多可能情況之一。 考慮[板球](http://en.wikipedia.org/wiki/Cricket)球隊與球員之間關係的另一種情況。每支球隊都必須有11名球員最低資格作爲一個團隊。這裏最小基數是11(11)。 – user1144616 2012-02-13 09:03:56

+0

@ user1144616:請將此示例添加到問題本身,它非常清楚地描述問題。 – 2012-02-13 10:44:21

+0

@ A.H .:增加了一個例子。 – user1144616 2012-02-13 11:45:26

3

沒有辦法,只用FOREIGN KEY約束來執行這樣的規則

1)一種方法是允許表格之間的循環引用(「默認」列,由jug建議)。這導致難以管理的雞與蛋問題,您將不得不使用可延遲的約束。另外,這個選項在某些DBMS中根本不可用。另一個缺點是對於一個橄欖球隊來說,你必須添加11個「默認」列(並且你必須處理一個雞和11個雞蛋的問題)!

2)另一種選擇是使用觸發器。

3)另一種選擇是在兩個表之間使用數據庫級約束。不確定是否有任何具有此功能的DBMS。除了典型的UNIQUE,PRIMARYFOREIGN KEY約束之外,大多數DBMS只有行級約束和限制(沒有子查詢等)。

4)另一種選擇是通過創建適當插入,更新執行這些規則和DELETE只能訪問兩個相關的表,並根據這些規則強制執行的完整程序。這是更好的方法(在我看來)。

5)還有一個選項,簡單的實現方法是使用標準的外鍵約束,強制執行1對多的關係,有一個觀點,顯示那些實際上有11個以上的玩家團隊。這個過程意味着你沒有實際執行你所要求的規則。但是可能的(也可能是我認爲可能)你不能負擔得起。如果足球運動員在事故中喪生,球隊不能再參加錦標賽,但仍然是一支球隊。所以,你可以定義兩個實體,團隊(基表)和ProperTeam(視圖),可以玩遊戲。例如:

CREATE VIEW AS ProperTeam 
(SELECT * 
    FROM Team 
    WHERE (SELECT COUNT(*) 
      FROM Player 
      WHERE Player.TeamId = Team.TeamId 
     ) >= 11 
) 

選項1和2的外觀,而「混亂」,但這只是個人意見,很多人喜歡觸發器。

我會選擇選項4,除非我能(「欺騙」和)實際上並不執行與備選方案5

+0

從數據庫視圖來看,選項5和4是很好的選擇,但客戶端的最常見的持久性框架無法應對它們 - 它們只是簡單地希望對一個實體使用簡單的「INSERT」,「UPDATE」,「SELECT」。說:「用這個程序做創作,用另一個做更新並通過視圖獲取數據」 - 玩得開心;-) – 2012-02-13 16:43:29

+2

@ A.H .:這就是爲什麼最常見的持久性框架是邪惡柺杖的一個例子。他們的承諾是簡化事情,甚至使您的應用程序不受數據影響(從不真正起作用)。實際上,他們將您鎖定在框架上,而框架本身保證不會比普通SQL便攜,並切斷RDBMS的一半功能。 – 2012-02-13 19:15:33

+1

@ErwinBrandstetter:持久性框架就像稅:幾乎沒有人喜歡他們,很多人恨他們(幾乎),從長遠來看,沒有人能逃避他們。 : - ] – 2012-02-13 19:54:02

1

是爲我工作所需的編碼是一個合理的量的方法約束(翻譯爲您所球隊/球員的問題):

  • 創建一個延遲的外鍵約束強制執行「每個球員都有一個」的關係。
  • 創建表隊剛剛一個觸發,這將檢查至少n玩家連接到團隊。觸發應該拋出一個異常,如果基數是不尊重,正如AdamKG指出。

這將強制約束,但作爲一個副作用,這也將只允許一個編碼的玩家一個新的團隊(也就是沒有拒絕它作爲一個鍵衝突)

start transaction; 
set constraints all deferred; 
insert player_1 with teamPK --teamPK is not yet in table team, will be below 
... 
insert player_n with teamPK 
insert teamPK --this fires the trigger which is successful. 
commit transaction; --this fires the foreign key deferred check, successful. 
方式

請注意,我通過使用自定義主鍵(對於teamPK)來做到這一點,例如一個獨特的團隊名稱,這樣我才能在將團隊實際插入表格團隊之前就知道團隊PK(而不是使用自動遞增的ID)。