?
This document uses PHP Chinese website manual Release
數(shù)據(jù)類型是限制我們可以在表里存儲什么數(shù)據(jù)的一種方法。不過,對于許多應(yīng)用 來說,這種限制實在是太粗糙了。比如,一個包含產(chǎn)品價格的字段應(yīng)該只接受正數(shù)。 但是沒有哪種標(biāo)準(zhǔn)數(shù)據(jù)類型只接受正數(shù)。另外一個問題是你可能需要根據(jù)其它字段 或者其它行的數(shù)據(jù)來約束字段數(shù)據(jù)。比如,在一個包含產(chǎn)品信息的表中,每個產(chǎn)品 編號都應(yīng)該只有一行。
對于這些問題,SQL允許你在字段和表上定義約束。約束允許你對數(shù)據(jù)施加任意控制。 如果用戶企圖在字段里存儲違反約束的數(shù)據(jù),那么就會拋出一個錯誤。這種情況同時 也適用于數(shù)值來自缺省值的情況。
檢查約束是最常見的約束類型。它允許你聲明在某個字段里的數(shù)值必須使一個 布爾表達式為真。比如,要強制一個正數(shù)的產(chǎn)品價格,你可以用:
CREATE TABLE products ( product_no integer, name text, price numeric CHECK (price > 0) );
如你所見,約束定義在數(shù)據(jù)類型之后,就好像缺省值定義一樣。缺省值和 約束可以按任意順序排列。一個檢查約束由一個關(guān)鍵字CHECK 后面跟一個放在圓括弧里的表達式組成。檢查約束表達式應(yīng)該包含受約束的字段, 否則這個約束就沒什么意義了。
你還可以給這個約束取一個獨立的名字。這樣就可以令錯誤信息更清晰,并且 在你需要修改它的時候引用這個名字。語法是:
CREATE TABLE products ( product_no integer, name text, price numeric CONSTRAINT positive_price CHECK (price > 0) );
因此,要聲明一個命名約束,使用關(guān)鍵字 CONSTRAINT 后面跟一個 標(biāo)識符(作為名字),然后再跟約束定義。如果你不用這個方法聲明約束,那么系統(tǒng)會自動 為你選擇一個名字。
一個檢查約束也可以引用多個字段。假設(shè)你存儲一個正常價格和一個折扣價, 并且你想保證折扣價比正常價低。
CREATE TABLE products ( product_no integer, name text, price numeric CHECK (price > 0), discounted_price numeric CHECK (discounted_price > 0), CHECK (price > discounted_price) );
頭兩個約束看上去很面熟。第三個使用了一個新的語法。它沒有附著在 某個字段上,而是在逗號分隔的字段列表中以一個獨立行的形式出現(xiàn)。 字段定義和約束定義可以按照任意順序列出。
我們稱頭兩個約束是"字段約束",而第三個約束是"表約束"(和字段定義分開寫)。 字段約束也可以寫成表約束,而反過來很可能不行,因為系統(tǒng)假設(shè)字段約束只 引用它所從屬的字段。PostgreSQL 并不強制這條規(guī)則, 但是如果你希望自己的表定義可以和其它數(shù)據(jù)庫系統(tǒng)兼容,那么你最好還是遵循這條 規(guī)則。上面的例子也可以這么寫:
CREATE TABLE products ( product_no integer, name text, price numeric, CHECK (price > 0), discounted_price numeric, CHECK (discounted_price > 0), CHECK (price > discounted_price) );
或者是
CREATE TABLE products ( product_no integer, name text, price numeric CHECK (price > 0), discounted_price numeric, CHECK (discounted_price > 0 AND price > discounted_price) );
這只是風(fēng)格的不同。
和字段約束一樣,我們也可以給表約束賦予名稱,方法也相同:
CREATE TABLE products ( product_no integer, name text, price numeric, CHECK (price > 0), discounted_price numeric, CHECK (discounted_price > 0), CONSTRAINT valid_discount CHECK (price > discounted_price) );
我們還要注意的是,當(dāng)約束表達式計算結(jié)果為 NULL 的時候,檢查約束會被認為是 滿足條件的。因為大多數(shù)表達式在含有 NULL 操作數(shù)的時候結(jié)果都是 NULL ,所以 這些約束不能阻止字段值為 NULL 。要確保一個字段值不為 NULL ,可以使用下一節(jié) 介紹的非空約束。
非空約束只是簡單地聲明一個字段必須不能是 NULL 。下面是一個例子:
CREATE TABLE products ( product_no integer NOT NULL, name text NOT NULL, price numeric );
一個非空約束總是寫成一個字段約束。非空約束在功能上等效于創(chuàng)建一個檢查 約束 CHECK (column_name IS NOT NULL) ,但在PostgreSQL里, 創(chuàng)建一個明 確的非空約束效率更高。缺點是你不能給它一個明確的名字。
當(dāng)然,一個字段可以有多個約束。只要一個接著一個寫就可以了:
CREATE TABLE products ( product_no integer NOT NULL, name text NOT NULL, price numeric NOT NULL CHECK (price > 0) );
它們的順序無所謂。順序并不影響約束檢查的順序。
NOT NULL 約束有個相反的約束:NULL 約束。它并不意味著該字段必須是空,因為這樣的字段也沒用。它只是定義了該字段 可以為空的這個缺省行為。在 SQL 標(biāo)準(zhǔn)里沒有定義NULL約束, 因此不應(yīng)該在可移植的應(yīng)用中使用它。在PostgreSQL 里面增加這個約束只是為了和其它數(shù)據(jù)庫系統(tǒng)兼容。不過,有些用戶喜歡它,因為 這個約束可以讓他們很容易在腳本文件里切換約束。比如,你可以從下面這樣開始:
CREATE TABLE products ( product_no integer NULL, name text NULL, price numeric NULL );
然后在需要的時候插入 NOT 關(guān)鍵字。
Tip: 在大多數(shù)數(shù)據(jù)庫設(shè)計里,主要的字段都應(yīng)該標(biāo)記為非空。
唯一約束保證在一個字段或者一組字段里的數(shù)據(jù)與表中其它行的數(shù)據(jù)相比是唯一的。 它的語法是:
CREATE TABLE products ( product_no integer UNIQUE, name text, price numeric );
上面是寫成字段約束,下面這個則寫成表約束:
CREATE TABLE products ( product_no integer, name text, price numeric, UNIQUE (product_no) );
如果一個唯一約束引用一組字段,那么這些字段用逗號分隔列出:
CREATE TABLE example ( a integer, b integer, c integer, UNIQUE (a, c) );
這樣就聲明了特定字段值的組合在整個表范圍內(nèi)是唯一的。但是這些字段中的 某個單獨值可以不必是(并且通常也確實不是)唯一的。
你也可以給唯一約束賦予一個自己定義的名字,方法與前面相同:
CREATE TABLE products ( product_no integer CONSTRAINT must_be_different UNIQUE, name text, price numeric );
添加一個唯一約束會在一個字段或者一組字段里自動創(chuàng)建一個唯一btree索引,以供在約束里運用。
通常,如果包含在唯一約束中的那幾個字段在表中有多個相同的行,就違反了唯一約束。 但是在這種比較中,NULL 被認為是不相等的。這就意味著,在多字段唯一約束的情況下, 如果在至少一個字段上出現(xiàn) NULL ,那么我們還是可以存儲同樣的這種數(shù)據(jù)行。這種行 為遵循 SQL 標(biāo)準(zhǔn),但是我們聽說其它 SQL 數(shù)據(jù)庫可能不遵循這個標(biāo)準(zhǔn)。因此如果你要 開發(fā)可移植的程序,那么最好仔細些。
從技術(shù)上講,主鍵約束只是唯一約束和非空約束的組合。所以,下面兩個表定義是等價的:
CREATE TABLE products ( product_no integer UNIQUE NOT NULL, name text, price numeric );
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric );
主鍵也可以約束多于一個字段;其語法類似于唯一約束:
CREATE TABLE example ( a integer, b integer, c integer, PRIMARY KEY (a, c) );
主鍵表示一個或多個字段的組合可以用于唯一標(biāo)識表中的數(shù)據(jù)行。這是定義 一個主鍵的直接結(jié)果。請注意:一個唯一約束實際上并不能提供一個唯一標(biāo)識, 因為它不排除NULL。這個功能對文檔目的和客戶應(yīng)用都很有用。比如,一個 可以修改行數(shù)值的GUI應(yīng)用可能需要知道一個表的主鍵才能唯一地標(biāo)識每一行。
添加一個主鍵會在主key運用的一字段或一組字段里自動創(chuàng)建一個唯一btree索引。
一個表最多可以有一個主鍵(但是它可以有多個唯一和非空約束)。關(guān)系型數(shù)據(jù)庫 理論告訴我們,每個表都必須有一個主鍵。PostgreSQL并不強制這個規(guī)則,但 我們最好還是遵循它。
外鍵約束聲明一個字段(或者一組字段)的數(shù)值必須匹配另外一個表中出現(xiàn)的數(shù)值。 我們把這個行為稱為兩個相關(guān)表之間的參照完整性。
假設(shè)你有個產(chǎn)品表,我們可能使用了好幾次:
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric );
假設(shè)你有一個存儲這些產(chǎn)品的訂單的表。我們想保證訂單表只包含實際 存在的產(chǎn)品。因此我們在訂單表中定義一個外鍵約束引用產(chǎn)品表:
CREATE TABLE orders ( order_id integer PRIMARY KEY, product_no integer REFERENCES products (product_no), quantity integer );
現(xiàn)在,我們不能創(chuàng)建任何其product_no沒有在產(chǎn)品表 中出現(xiàn)的訂單。
在這種情況下我們把訂單表叫做引用表, 而產(chǎn)品表叫做被引用表。 同樣,也有引用字段和被引用字段。
你也可以把上面的命令簡寫成:
CREATE TABLE orders ( order_id integer PRIMARY KEY, product_no integer REFERENCES products, quantity integer );
因為如果缺少字段列表的話,就會引用被引用表的主鍵。
一個外鍵也可以約束和引用一組字段。同樣,也需要寫成表約束的形式。 下面是一個捏造出來的語法例子:
CREATE TABLE t1 ( a integer PRIMARY KEY, b integer, c integer, FOREIGN KEY (b, c) REFERENCES other_table (c1, c2) );
當(dāng)然,被約束的字段數(shù)目和類型需要和被引用字段數(shù)目和類型一致。
和平常一樣,你也可以給外鍵約束賦予自定義的名字。
一個表可以包含多于一個外鍵約束。這個特性用于實現(xiàn)表之間的多對多關(guān)系。 比如你有關(guān)于產(chǎn)品和訂單的表,但現(xiàn)在你想允許一個訂單可以包含多種產(chǎn)品 (上面那個結(jié)構(gòu)是不允許這么做的),你可以使用這樣的結(jié)構(gòu):
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric ); CREATE TABLE orders ( order_id integer PRIMARY KEY, shipping_address text, ... ); CREATE TABLE order_items ( product_no integer REFERENCES products, order_id integer REFERENCES orders, quantity integer, PRIMARY KEY (product_no, order_id) );
注意最后的表的主鍵和外鍵是重疊的。
我們知道外鍵不允許創(chuàng)建和任何產(chǎn)品都無關(guān)的訂單。但是如果一個訂單 創(chuàng)建之后其引用的產(chǎn)品被刪除了怎么辦?SQL 也允許你處理這個問題。 簡單說,我們有幾種選擇:
不允許刪除一個被引用的產(chǎn)品
同時也刪除訂單
其它的?
為了說明這個問題,我們對上面的多對多關(guān)系制定下面的策略:如果有人想 刪除一種仍然被某個訂單引用的產(chǎn)品(通過order_items),那么就不允許這么做。 如果有人刪除了一個訂單,那么訂單項也被刪除。
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric ); CREATE TABLE orders ( order_id integer PRIMARY KEY, shipping_address text, ... ); CREATE TABLE order_items ( product_no integer REFERENCES products ON DELETE RESTRICT, order_id integer REFERENCES orders ON DELETE CASCADE, quantity integer, PRIMARY KEY (product_no, order_id) );
限制和級聯(lián)刪除是兩種最常見的選項。RESTRICT禁止刪除被引用的行。 NO ACTION的意思是如果在檢查約束的時候 還存在任何引用行,則拋出錯誤;如果你不聲明任何東西,那么它就是缺省的行為。 這兩個選擇的實際區(qū)別是:NO ACTION允許約束檢查推遲到事務(wù)的 晚些時候,而 RESTRICT不行。CASCADE聲明在刪除 一個被引用的行的時候,所有引用它的行也會被自動刪除掉。在外鍵字段上的動作還有兩個選項: SET NULL和SET DEFAULT , 它們導(dǎo)致在被引用行刪除的時候,將引用它們的字段分別設(shè)置為NULL和缺省值。 請注意這些選項并不能讓你逃脫被觀察和約束的境地。比如,如果一個動作聲明 SET DEFAULT,但是缺省值并不能滿足外鍵,那么該動作就會失敗。
與ON DELETE類似的還有ON UPDATE選項, 它是在被引用字段修改(更新)的時候調(diào)用的,可用的動作是一樣的。
從一個引用表中的DELETE一行或者從引用字段中UPDATE一列都 需要掃描一次引用表以便從行中匹配老的數(shù)值,給引用列建索引一直是一個好主意。因為 這不總是被需要,而且怎么去建立索引還有許多其它的選擇,外鍵的約束聲明不能在引用字段里自動生成索引。
有關(guān)更新和刪除數(shù)據(jù)的更多信息可以在Chapter 6里找到。
最后,我們應(yīng)該說明的是,一個外鍵必須要么引用一個主鍵,要么引用一個唯一約束。 如果外鍵引用了一個唯一約束,那么在如何匹配 NULL 這個問題上還有一些其它的可能性。 這些東西都在CREATE TABLE 中解釋。
排除約束保證如果任何兩行被在聲明的字段里比較或者用聲明的操作表達, 至少有一個操作比較會返回錯誤或空值。 句法是:
CREATE TABLE circles ( c circle, EXCLUDE USING gist (c WITH &&) );
更多細節(jié)也請參考CREATE TABLE ... CONSTRAINT ... EXCLUDE。
添加一個排除約束會在約束聲明里自動創(chuàng)建一個聲明類型的索引。