使用表繼承而不是映射表
這似乎是一個非常常見的場景:幾種類型都構成相同的子類型。
這通常看起來像這樣:
-- 'name' is unique per parent record CREATE TABLE sometype ( sometype_id serial PRIMARY KEY, name text ); CREATE TABLE foo (foo_id serial PK); CREATE TABLE bar (bar_id serial PK); CREATE TABLE foo_sometype ( foo_id int4, sometype_id int4 ); CREATE TABLE bar_sometype ( bar_id int4, sometype_id int4 );
這很好,但查詢起來很麻煩。我認為這可能更清潔:
-- 'name' is unique per parent record CREATE TABLE sometype ( name text ); CREATE TABLE foo_sometype ( foo_id int4 ) INHERITS(sometype); CREATE TABLE bar_sometype ( bar_id int4, ) INHERITS(sometype);
我喜歡這個:
- 加入簡單(with
USING
)- 無需為“sometype”添加代理鍵,它明確是“foo”和“bar”的組件
不過,這似乎是一種非典型的繼承用法。
有什麼理由不這樣做?
論繼承的“不足”
請注意,Pg 繼承的標準警告僅在表繼承用於直接建模類繼承時才相關,這不是我在這裡所做的。事實上,為了讓它工作,我需要繼承來表現它的行為方式。
我幾乎希望他們將其稱為“繼承”以外的其他東西,因為這種行為非常合乎邏輯,並且“缺點”僅與一個案例相關。
優於手動複製表結構的好處
正如 Evan 所指出的,我可以手動創建看起來與我描述的完全一樣的“foo_sometype”和“bar_sometype”,但我認為繼承結構有幾個顯著的好處:
- 繼承關係明確地將 ‘foo_sometype’ 和 ‘bar_sometype’ 定義為相同類型,而不僅僅是恰好具有相同列的兩個表。
- 通過父表進行未來的模式更改可以減少意外分歧的機會(通過一些工作,這實際上可以強制執行)。
- 更重要的是,可以針對父表生成客戶端程式碼,並將其應用於僅更改表名的子表,並且(再次)確信執行了表結構。
所以,作為一個人為的例子,Foo 和 Bar 可以有一個 HasSomeTypeList 特徵,它抽象了所有“sometype”操作,並且知道兩個表都可以映射到 SomeType 類。
表示 Foo/Bar 關係,無論是建模為 trait 還是繼承,都是這裡的最終目標。
順便說一句,對於天真的使用者/查詢編寫者——他們不希望進行模式更改——這兩種方式看起來是一樣的。
繼承是我不會觸及的那些特性之一。AFAIK,它在內部用於某些容量的複制和分區。我不確定它是否是為了最終使用者使用而設計的。
具體的技術缺點
UNIQUE 和 REFERENCES 的缺點
該文件涵蓋了CAVEAT 部分中的一些缺點(以下很重要)。
- 如果我們將 parent.name 聲明為 UNIQUE 或 PRIMARY KEY,這不會阻止子表的行名稱與父表中的行重複。預設情況下,這些重複的行會顯示在來自父級的查詢中。事實上,預設情況下 child 根本沒有唯一約束,因此可以包含多個具有相同名稱的行。您可以為子級添加唯一約束,但這不會阻止與父級相比的重複。
- 類似地,如果我們指定 parent.name REFERENCES 某個其他表,則此約束不會自動傳播到子表。在這種情況下,您可以通過手動將相同的 REFERENCES 約束添加到子級來解決它。
- 指定另一個表的列 REFERENCES parent(name) 將允許另一個表包含父名稱,但不允許包含子名稱。這種情況沒有好的解決方法。
開發 INHERITs 進展緩慢
這些缺陷在 1996 年發布的7.3 文件中首次提到,儘管它們在實現繼承後就存在
這個缺陷可能會在未來的某個版本中得到修復。
唯一的變化是使2010 年發布的8.0 文件中的缺陷更加明確和詳細。
這些缺陷可能會在未來的某個版本中得到修復,但與此同時,在確定繼承是否對您的問題有用時需要相當小心。
祝你好運,等待未來的發布。而且,您談論的某些功能並不是構圖所獨有的,
保存“鑰匙”沒有實際意義
- ‘sometype’ 上沒有代理鍵,它明確地是一個組合
這與製作
sometype
屬性列表並直接連結到它有什麼不同?CREATE TABLE sometype (sometype_name text PRIMARY KEY); CREATE TABLE foo (foo_id serial PRIMARY KEY); CREATE TABLE foo_sometype ( foo_id int REFERENCES foo, sometype_name text REFERENCES sometype, PRIMARY KEY ( foo_id, sometype_name ) );
現在您甚至不必加入
foo_sometype
即可sometype
獲得sometype.sometype_name
.表分區
除了所有這些問題,即將發布的 PostgreSQL 10 表分區版本變得更糟
不允許多重繼承,分區和繼承不能混用
所以你想要繼承?放棄分區,這實際上具有真正的規劃者優勢。
更改表
唉,
ALTER TABLE
它的註釋中也列出了很多缺點,如果一個表有任何後代表,則不允許添加、重命名或更改列的類型,或重命名父表中的繼承約束,而不對後代做同樣的事情。也就是說,ALTER TABLE ONLY 將被拒絕。這可確保後代始終具有與父級匹配的列。
$$ … $$遞歸 DROP COLUMN 操作將刪除後代表的列,僅當後代不從任何其他父級繼承該列並且從未有該列的獨立定義時。非遞歸 DROP COLUMN(即 ALTER TABLE ONLY … DROP COLUMN)永遠不會刪除任何後代列,而是將它們標記為獨立定義而不是繼承。$$ … $$TRIGGER、CLUSTER、OWNER 和 TABLESPACE 操作永遠不會遞歸到後代表;也就是說,它們總是表現得好像只指定了一樣。僅對未標記為 NO INHERIT 的 CHECK 約束重複添加約束。
結論
我不認為很多人使用繼承。我從來沒有在野外見過它。數據庫中的繼承增加了學習曲線,並且一些特性最好不要管。您不必為他們找到應用程序。
提到的缺陷不是不使用繼承的理由!繼承在這裡的工作類似於具有獨立對象的類繼承。你可能有一個類/表“水果”和一個類/表“蘋果”和“橙子”。由於蘋果和橙子從水果中繼承了它們的元定義,因此您可以通過水果獲取它們。然而,它們是獨立的類,具有獨立的列舉,還有什麼你可以期待的……
如果您確實需要防止衝突:在主表上定義觸發器或檢查/外鍵(不包含)。
但是,請不要僅僅因為您對 PostgreSQL 中繼承的工作方式不滿意,就不要阻止人們使用它!獨立的子類或表格很棒!如果你需要一些依賴 - 以你需要的方式實現它。有很多很好的教程,包括官方文件。這是另一個例子:繼承——另一個喜歡 PostgreSQL 的理由。
我也在我的一個項目中使用繼承,儘管可能存在主鍵衝突(我的父表沒有定義主鍵,我的模型不需要它)。
還有一些方法可以使用繼承來提高性能,不僅僅是 ORM。
難不等於壞。