1. 程式人生 > >數據庫設計層次3:構建表

數據庫設計層次3:構建表

num 有一個 val 單個 build del 種類型 最終 iar

源自:Stairway to Database Design Level 3: Building Tables

作者Joe Celko

翻譯:劉瓊濱 謝雪妮 許雅莉 賴慧芳

譯文:

對於設計和創建數據庫完全是個新手?沒關系,Joe Celko, 世界上讀者數量最多的SQL作者之一,會告訴你這些基礎。和往常一樣,即使是最專業的數據庫老手,也會給他們帶來驚喜。Joe是DMBS雜誌是多年來最受 讀者喜愛的作者。他在美國、英國,北歐,南美及非洲傳授SQL知識。他在ANSI / ISO SQL標準委員會工作了10年,為SQL-89和SQL-92標準做出了傑出貢獻。

有很多類型的表,每個表都有它們特定規則和完整性約束的要求。無論需求是什麽,表層級的約束會確保被執行,維護數據的完整性。

在表裏,列只會出現一次。這樣做是有道理的;如果你兩次記錄某人的鞋子大小,這將是多余的,當列不一致時是混淆的。現在我們可以有表層級的在每行的列裏的檢查(CHECK)約束。這和之前列上的(CHECK)檢查並沒有啥區別。它們可以在CREATE TABLE語句裏,多個列聲明裏命名並出現,不附加到任何行。例如:

CONSTRAINT Valid_Employee_Age-- don‘t hire people before they are born

CHECK (emp_birth_date < emp_hire_date)

一般不把檢查組合成一個大的CHECK()子句,錯誤信息會包含約束名稱,因此獨立的約束相比單個復雜命名的約束,能讓你更清楚的發現問題。

接下來是我們的冗余問題,在表層級我們想每個行因同個原因而唯一,這可以通過約束實現。兩個表層級的約束是UNIQUE和PRIMARY KEY,它們可以是單列或多列組合。

UNIQUE約束表示在表裏,列或列的組合是唯一的。但在列或多個列中有NULL,如果它是唯一值,我們還是允許的。PRIMARY KEY聲明,與對於表裏面的所有列,NOT NULL且UNIQUE有同樣的效果,但按照慣例,一個表只能有一個PRIMARY KEY聲明,這些列用來作為表之間的其他約束,但現在不在乎這些。

唯一性約束如何使用取決於表的類型,一般來說,我們可以將一個表分成三種類型:

1.實體(entity)

2.關系(relationship)

3.輔助(auxiliary)

實體表是一組相同類型的元素的集合,它們由列所建模的屬性定義。每一行都是這類元素的一個實例,每一行都有相同的列。如果你能看到它的感覺,看到或觸摸它,那它就是一個實體。實體表的名稱不應該是單數(除非真的這個集合中只有一個成員),因為它為一個集合建模,命名應該為復數,可以的話,則用集合命名。例如,“Employee”不好,“Employees”更好,“Personnel”最好。“Tree”不好,“Trees”更好,“Forest”最好。你可以添加你自己的示例。

實體也分弱強,一個強大的實體存在有它自身的優點,而一個脆弱的實體存在於一個或多個強大的實體中。就比如你需要購買才能打折。

關系表指的是一個或多個實體表,並且它們之間建立關系。關系可以有它自己委外引用實體的屬性。結婚登記號屬於婚姻,而不屬於丈夫,妻子或牧師。

關系級別是關系裏實體的個數,二元關系有2個實體,在實際應用中我們喜歡它們,因為它們簡單;二元叠代關系關聯到實體本身,一般的n元關系涉及n個實體,就像有買家,賣家和銀行的房貸。通常不能把n元關系分解為二元關系。成員的關系可以是可選或必須的。可選的關系表示我們可以有一類的0實體——並不是所有的買賣都有折扣。

關系基數是對於每2個實體,相關出現的實際數量。關系的基本連接類型有:1:1,1:n,和n:n。這些術語通常是符合可選(0或更多)或必須的(1或更多)的關系。

1:1關系是一個實體A的最多一個實例與實體B的一個實例關聯的時候。例如,拿通常的丈夫和妻子的關系。每個丈夫有且只要一個妻子;每個妻子有且只有一個丈夫。在這個例子都是必須一個的。

1:n關系是實體A的一個實例,對於實體B的一個實例有0個,一個或多個實體B的實例,實體A的實例只有一個的時候。一個例子會是一個部門有很多員工;每個員工分配到一個部門。取決於你的業務規則,你會允許未分配部門的員工或空的部門。

n:n關系,有時稱作非特定的,對於實體A的一個實例,有0個,一個或多個實體B的實例,並且對於實體B的一個實例,有0個,一個或多個實體A的實例。這樣的例子可以是披薩和客戶。

輔助表不是實體也不是關系;它提供信息。它們是像日歷或在SQL裏替換計算的查詢表(look up tables)。它們經常被誤解被當實體或關系表對待。

我們來具體說下。銷售訂單是客戶(實體)和我們的庫存(實體)之間的關系。訂單明細是存在的弱實體,因為我們有訂單。這個關系有一個不是庫存或客戶一部分的訂單號。運費從輔助表獲得。對於這個例子,這裏我用了一些骨架表。對於訂單項目,我使用GTIN(Global Trade Item Number),對於客戶,我使用GUNS(Data Universal Numbering System)。在你設計數據庫的時候,都要記得先看看行業標準。

CREATE TABLE Sales_Orders

(order_nbr INTEGER NOT NULL PRIMARY KEY

CHECK (order_nbr > 0),

customer_duns CHAR(9) NOT NULL,

order_shipping_amt DECIMAL (5,2) NOT NULL

CHECK (shipping_amt >= 0.00),

etc);

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL,

gtin CHAR(15) NOT NULL,

PRIMARY KEY (order_nbr, gtin),

item_qty INTEGER NOT NULL

CHECK (item_qty > 0),

item_unit_price DECIMAL (8,2) NOT NULL

CHECK (item_unit_price >=0.00));

CREATE TABLE Customers

(customer_duns CHAR(9) NOT NULL PRIMARY KEY

CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

etc);

CREATE TABLE Inventory

(gtin CHAR(15) NOT NULL PRIMARY KEY

CHECK (gtin LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

onhand_qty INTEGER NOT NULL

CHECK (onhand_qty >= 0),

我們可以看到訂單表是客戶和庫存間的關系。訂單有它們自己的主鍵(order_nbr),但沒有東西強制我們使用有效的客戶DUNS號或對於我們庫存裏的產品GTIN號。事實上,我可以插入顯然無效的DUNS和GTIN碼到訂單表,現在就這樣聲明。

這就是我們要引入REFERENCES子句的地方。它是讓我們從數據模型強制所有基數和程度的東西。引用(reference)不是個鏈接或指針。這些是物理概念,引用是個邏輯概念,我們不知道它如何實現。它強制的是,在引用表裏,引用表列符合單行的規則。這意味著在引用表裏的行必須唯一;默認情況下,在引用表裏可以使用主鍵(PRIMARY KEY),但不必這樣。在引用表的值可以稱為外鍵(Foreign Keys)——它們不在它們的表裏,但在架構裏的其它地方。

這是上面有更多信息的主要架構:

CREATE TABLE Sales_Orders

(order_nbr INTEGER NOT NULL PRIMARY KEY

CHECK (order_nbr > 0),

customer_duns CHAR(9) NOT NULL

REFERENCES Customers(customer_duns),

order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL

CHECK (shipping_amt >= 0.00),

etc);

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL

REFERENCES Orders(order_nbr),

gtin CHAR(15) NOT NULL

REFERENCES Inventory(gtin),

PRIMARY KEY (order_nbr, gtin),-- two column key

item_qty INTEGER NOT NULL

CHECK (item_qty > 0),

item_unit_price DECIMAL (8,2) NOT NULL

CHECK (item_unit_price >= 0.00));

CREATE TABLE Customers

(customer_duns CHAR(9) NOT NULL PRIMARY KEY

CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

etc);

註意,在DUNS和GTIN是主鍵的地方,我們只有CHECK()約束,而不是在它們出現的引用表裏。實體表,客戶和庫存都被引用;關系表,訂單,引用了其它表。這是一般模式,但不是固定的。

這個子句的多列形式如下:

FOREIGN KEY (order_nbr, gtin)

REFERENCES Sales_Order_Details(order_nbr, gtin)

在FOREIGN KEY子句的列是引用表裏需要匹配的引用主鍵,列對列,但可能會有不同的名稱。我可以通過在相應的地方放置唯一約束來設置1:1,1:n和n:n的關系。作為輔助表的一個例子,我們可以根據訂單的總額來計算運輸成本,這張表看起來像這樣:

CREATE TABLE Shipping_Costs

(start_order_amt_tot DECIMAL (10,2) NOT NULL,

end_order_amt_tot DECIMAL (10,2) NOT NULL,

CONSTRAINT Valid_Shipping_Range

CHECK (start_order_amt_tot < end_order_amt_tot),

PRIMARY KEY (start_order_amt_tot, end_order_amt_tot),

shipping_amt DECIMAL (5,2) NOT NULL

CHECK (shipping_amt > 0.00));

雖然我們在輔助運費表上聲明了主鍵(PRIMARY KEY),但它不像實體的主鍵——沒有驗證或核查,也不是標識符。使用這個表,我們將以類似查詢:

SELECT shipping_amt

FROM Shipping_Costs

WHERE <order amount total> BETWEEN start_order_amt_tot AND end_order_amt_tot;

作為練習,嘗試寫下會從重復和斷層上阻止開始和結束範圍的約束。如果你需要的話,可以重新設計表。

在修正後的主要架構裏,當你試著對一個沒有沒有庫存的產品下單時,你會收到錯誤提示“沒有庫存!”,這樣的話,你可以試下別的。但如果你嘗試從庫存裏刪除產品,你同樣也會收到錯誤提示“額,有人已經下了此產品的訂單”,因此在從庫存裏刪它之前,你必須到每個訂單用別的值或NULL值來替換它。

這裏就是引用完整性(Declarative Referential Integrity (DRI))用的地方。語法是:

ON DELETE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]

ON UPDATE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]

刪除和更新是所謂的“數據基礎事件(data base events)”;當它們發生到表時,就會發生DRI操作。

NO ACTION:事務回滾,你收到提示。當有簡單的REFERENCES子句時的默認操作。

SET DEFAULT:引用的列通過事件改變,但引用的列值會修改為它們的默認值。當然,引用的列在它們上面要有聲明的默認值。這些默認需要在引用表裏。

SET NULL:引用的列通過事件改變,但引用的列值會修改為NULL。當然,引用的列允許NULL值。這是引入NULL值“無罪推定(benefit of the doubt)”的地方。

CASCADE:引用的列通過事件改變,這些值會級聯到引用的列。實際中這是最重要的選項。例如,如果我們想停止一個產品,我們可以從庫存裏刪除它,ON DELETE CASCADE會讓SQL引擎會在Sales_Order_Details自動刪除匹配的行。同樣,如果在庫存裏更新一個項目,ON UPDATE CASCADE會用新值自動替換引用的列。

在這些操作完成後,引用完整性約束還是有效的。這是最終的架構:

CREATE TABLE Sales_Orders

(order_nbr INTEGER NOT NULL PRIMARY KEY

CHECK (order_nbr > 0),

customer_duns CHAR(9) NOT NULL

REFERENCES Customers(customer_duns)

ON UPDATE CASCADE

ON DELETE CASCADE,

order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL

CHECK (shipping_amt >= 0.00),

etc);

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL

REFERENCES Orders(order_nbr)

ON UPDATE CASCADE

ON DELETE CASCADE,

gtin CHAR(15) NOT NULL

REFERENCES Inventory(gtin)

ON UPDATE CASCADE

ON DELETE CASCADE,

PRIMARY KEY (order_nbr, gtin),-- two column key

item_qty INTEGER NOT NULL

CHECK (item_qty > 0),

item_unit_price DECIMAL (8,2) NOT NULL

CHECK (item_unit_price >= 0.00));

看看下面的情況發生時,你覺得會發生什麽?

1.一個客戶走了,我們刪除它。

2.我們修改Lawn Gnome雕像為更有品味的Pink Flamingo。

3.我們停止銷售Pink Flamingo。

4.在1-3步驟後,有人嘗試下Lawn Gnome訂單。

顯然,我留下未處理的問題和其他東西,但我們會接觸這些。

原文鏈接:

http://www.sqlservercentral.com/articles/Stairway+Series/69927/

數據庫設計層次3:構建表