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