SQL Server中的約束(constraints)詳解

一、約束的分類

在SQL Server中,有3種不同類型的約束。

  • 實體約束
    實體約束是關於行的,比如某一行出現的值就不允許出現在其他行,例如主鍵。
  • 域約束
    域約束是關於列的,對於所有行,某一列有那些約束,例如CHECK約束。
  • 參照完整性約束
    如果某列的值必須與其他列的值匹配,那就意味著需要一個參照完整性約束,例如外鍵。

二、約束命名

在學習約束之前,首先來瞭解下為約束命名需要註意哪些地方。

SQL Server在我們不提供名稱時,會自動創建名稱,但是由系統自動創建的名稱並不是特別有用。

例如,系統生成的主鍵名稱可能是這樣的:PK_Employees_145C0A3F。

PK代表主鍵(primary key),Employees代表在Employees表中,而剩下的“145C0A3F”部分是為瞭保證唯一性而隨機生成的值。隻有通過腳本創建才會得到這種值,如果是通過Managerment Studio創建表,那麼就直接是PK_Employees。

對於系統自動生成的Check約束名稱如:CK_Customers_22AA2996。CK代表這是一個Check約束,Customers代表是在Customers表中,後面的22AA2996還是一個隨機數。如果一個表中有多個Check約束,則命名可能如下:

CK_Customers_22AA2996

CK_Customers_25869641

CK_Customers_267ABA7A

如果你需要修改這些約束其中的一個,那麼你很難分辨這些約束到底是哪一個。

因此,為瞭能夠一眼看上去就知道這個約束是用來幹什麼的,我們應該使用一種簡單明瞭的短語來進行命名。

例如要確保某一列電話號碼格式正確的約束,我們可以使用命名CK_Customers_PhoneNo這樣的短語來命名。

總之命名要做到以下幾點:

  • 一致性
  • 通俗易懂
  • 滿足以上兩個條件的情況下簡化名稱。

三、主鍵約束

  主鍵是每行的唯一標識符,僅僅通過它就能準確定位到一行,其中主鍵列在整個表中不能有重復,必須包含唯一的值(不能為NULL)。由於主鍵在關系數據庫中的重要性,因此它是所有鍵和約束中最重要的。

  下面來說說主鍵的創建方式

1、在創建表的時候創建主鍵約束。

create table customer
(
    customerId        int identity    not null  primary key,--創建主鍵約束
    CustomerName    nvarchar(30)    not null
);

2、在已存在的表上創建主鍵約束

現在假設已經存在瞭一張表,但是還沒有主鍵約束:

alter table person
    add constraint PK_Employee_Id--外鍵名稱
    primary key(personId)--personId 字段名

alter名稱告訴SQL Server如下信息:

  • 添加瞭一些內容到表中(也可以刪除表中的某些內容)
  • 添加瞭什麼內容(一個約束)
  • 對約束的命名(允許以後直接訪問約束)
  • 約束的類型(主鍵約束)
  • 約束應用於哪個列。

3、復合主鍵的創建

如果實在Management Studio中,創建復合主鍵,隻需要按住Ctrl鍵,選中兩個列,然後設置為主鍵就OK瞭,非常簡單。下面主要講述使用T-SQL創建復合主鍵的方法:

ALTER TABLE 表名 WITH NOCHECK 
ADD CONSTRAINT [PK_表名] 
PRIMARY KEY NONCLUSTERED ( [字段名1], [字段名2] )

在多對多聯系中,常常會有一張表來描述其他兩張表的關系,就以此讀者和書為例子:

ALTER TABLE ReaderAndBook 
ADD CONSTRAINT [PK_ReaderAndBook] 
PRIMARY KEY NONCLUSTERED ( ReaderId, BookId )

四、外鍵約束

外鍵既能確保數據完整性,也能表現表之間的關系。添加瞭外鍵之後,插入引用表的記錄要麼必須被引用表中被引用列的某條記錄匹配,要麼外鍵列的值必須設置為NULL。

外鍵和主鍵不一樣,每個表中的外鍵數目不限制唯一性。在每個表中,每一有-~253個外鍵。唯一的限制是一個列隻能引用一個外鍵。一個列可以被多個外鍵引用。

4.1、創建表的時候創建外鍵

create table orders
(
    orderId       int identity    not null   primary key,
    customerId    int             not null   foreign key references customer(customerId)--約束類型-外鍵-引用表(列名)
);

4.2、在已存在的表中添加一個外鍵

假設上面的代碼去掉瞭添加外鍵行,那麼可以書寫代碼如下:

alter table orders
    add constraint FK_Orders_CustomerId        --添加約束 名稱
    foreign key (customerId)   references customer(customerId)    --外鍵約束,外鍵列名,被引用列名

剛添加的約束和之前添加的約束一樣生效,如果某行引用customerId不存在,那麼就不允許把該行添加到Orders表中。

4.3、級聯動作

外鍵和其他類型鍵的一個重要區別是:外鍵是雙向的,即不僅是限制子表的值必須存在於父表中,還在每次對父表操作後檢查子行(這樣避免瞭孤行)。SQL Server的默認行為是在子行存在時“限制”父行被刪除。然而,有時會自動刪除任何依賴的記錄,而不是防止刪除被引用的記錄。同樣在更新記錄時,可能希望依賴的記錄自動引用剛剛更新的記錄。比較少見的情況是,你可能希望將引用行改變為某個已知的狀態。為此,可以選擇將依賴行的值設置為NULL或者那個列的默認值。

這種進行自動刪除和自動更新的過程稱為級聯。這種過程,特別是刪除過程,可以經過幾層的依賴關系(一條記錄依賴於另一條記錄,而這另一條記錄又依賴其他記錄)。

在SQL Server中實現級聯動作需要做的就是修改外鍵語法-隻需要在添加前面加上ON子句。例如:

alter table orders
    add constraint FK_Orders_CustomerId        --添加約束 名稱
        foreign key (customerId)    references customer(customerId)    --外鍵約束,外鍵列名,被引用列名
        on update     no action    --默認修改時不級聯更新子表
        on delete     cascade      --刪除時級聯刪除依賴行

當在進行級聯刪除時,如果一個表級聯瞭另一個表,而另一個表又級聯瞭其他表,這種級聯會一直下去,不受限制,這其實是級聯的一個危險之處,很容易一個不小心刪掉大量數據。

級聯動作除瞭no action,cascade之外,還有set null和set default。後兩個是在SQL Server2005中引入的,如果要兼容到SQL Server2000的話,要避免使用這兩個級聯動作。但是他們的才做是非常簡單的:如果執行更新而改變瞭一個父行的值,那麼子行的值將被設置為NULL,或者設置為該列的默認值(不管SET NULL還是SET DEFAULT)。

五、唯一約束

唯一約束與主鍵比較相似,共同點在於它們都要求表中指定的列(或者列的組合)上有一個唯一值,區別是唯一約束沒有被看作表中記錄的唯一標識符(即使你可以按這樣的方式使用也有效),而且可以有多個唯一約束(而在每個表中隻能有一個主鍵)。

一旦建立瞭唯一約束,那麼指定列中的每個值必須是唯一的。如果更新或者插入一條記錄在帶唯一約束的列上有已經存在的值的記錄,SQL Server將拋出錯誤,拒絕這個記錄。

和主鍵不同,唯一約束不會自動防止設置一個NULL值,是否允許為NULL由表中相應列的NULL選項的設置決定,但即使確實允許NULL值,一張表中也隻能夠插入一個NULL值(如果允許多個,那就不叫唯一瞭)。

在已存在的表上創建唯一約束:

alter table Account
    add constraint AK_AccountName    --約束名
    unique (Account_Name)    -- 列名

AK代表替換鍵(Alternate Key),唯一約束也叫替換鍵。

主鍵和唯一約束的區別:

  •     主鍵約束不允許出現NULL值。任何索引的索引鍵都不允許包含null值。但唯一約束允許包含NULL值,但唯一約束把兩個NULL值當作重復值,所以施加瞭唯一約束的每一列隻允許包含一個NULL值。
  •     創建主鍵時會自動創建聚集索引,除非當前表中已經含有瞭聚集索引或是創建主鍵時指定瞭NONCLUSTERED關鍵字。
  •     創建唯一約束時會自動創建非聚集索引,除非你指定瞭CLUSTERED關鍵字並且當前表中還沒有聚集索引。
  •     每個表中隻能有一個主鍵,但可以由多個唯一約束。

六、CHECK約束

CHECK約束約束可以和一個列關聯,也可以和一個表關聯,因為它們可以檢查一個列的值相對於另外一個列的值,隻要這些列都在同一個表中以及值是在更新或者插入的同一行中。CHECK約束還可以用於檢查列值組合是否滿足某一個標準。

可以像使用where子句一樣的規則來定義CHECK約束。CHECK約束條件的示例如下:

  • 限制Month列為合適的數字:BETWEEN 1 AND 12
  • 正確的SSN格式:LIKE'[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]'
  • 限制為一個快遞公司的特定列表:IN('UPS','Fed Ex',EMS')
  • 價格必須為正數:UnitPrice >= 0
  • 引用同一行中的另外一列:ShipDate >= OrderDate

上面給出的列表隻是一小部分,而條件實際上市無限多的。幾乎所有可以放到where子句的條件都可以放到該約束中。而且和其他選擇(規則和觸發器)相比,CHECK約束執行速度更快。

在已存在的表中添加一個CHECK約束:

alter table Account
    add constraint CN_AccountAge
    check  (Account_Age > 18);    -- 插入年齡必須大於18

如果此時視圖添加一條不滿足的記錄,將報如下錯誤:

insert into Account values (22,'洪',17)
消息 547,級別 16,狀態 0,第 1 行
INSERT 語句與 CHECK 約束"CN_AccountAge"沖突。該沖突發生於數據庫"Nx",表"dbo.Account", column 'Account_Age'。
語句已終止。

七、DEFAULT約束

和所有約束一樣,DEFAULT約束也是表定義的一個組成部分,它定義瞭當插入的新行對於定義瞭默認約束的列未提供相應數據時該怎麼辦。可以定義它為一個字面值(例如,設置默認薪水為0,或者設置字符串列為"UNKNOWN"),或者某個系統值(getdate())。

對於DEFAULT約束,要瞭解以下幾個特性:

1、默認值隻在insert語句中使用。在update語句和delete語句中被忽略。

2、如果在insert語句中提供瞭任意值,那就不使用默認值。

3、如果沒有提供值,那麼總是使用默認值。

值得註意的是,update命令的規則有一個例外,如果顯示說明使用默認值就是例外。可以通過使用關鍵字DEFAULT表示更新的值設置為默認值。

7.1在創建表時定義DEFAULT約束:

create table person
(
    person_id int identity not null  primary key,
    person_name nvarchar(30) not null   default '無名氏',
    person_age int not null
)

在執行語句後:

insert into person (person_age) values(24)

表中被插入一條記錄如下:

7.2在已存在的表上添加DEFAULT約束:

alter table person
    add constraint CN_DefaultName
    default    '無名氏'  for person_name

八、禁用約束

有時我們想暫時或永久地消除約束。但是SQL Server並沒有提供刪除約束的方法。SQL Server隻允許禁用外鍵約束或CHECK約束,而同時保持約束的完整性。

禁用一個數據完整性規則通常是因為已經有無效數據瞭。這樣的數據通常分為以下兩類:

1、在創建約束時已經在數據庫中的數據

2、在約束創建以後希望添加的數據

SQL Server允許禁用完整性檢查一段時間來對例外的無效數據作處理,然後再重新啟用完整性(不是物理刪除數據完整性約束)。

註意:不能禁用主鍵約束或者唯一約束

8.1、在創建約束時,忽略檢查之前的不滿足數據

要添加一個約束,但是有不應用到已存在的數據中,可以再執行Alter Table語句添加約束時使用WITH NOCHECK選項。

按照上面創建Check約束的方法,已經Alter Table時,表中本身已經存在不符合的數據,那麼Alter Table操作將被SQL Server拒絕執行。除非已經存在的所有數據都滿足CHECK約束的條件,否則SQL Server不會執行創建約束的命令。要解決這個問題,我們可以添加WITH NOCHECK。

我們先新建一個表隻有3個字段的表,Id、姓名、年齡,並在裡面插入一條不滿足要求的數據:

insert into Account values (23,'洪',17)

然後執行添加約束命令:

alter table Account
    add constraint CN_AccountAge18
    check (Account_Age > 18);    -- 插入年齡必須大於18

SQL Server報一下錯誤:

消息 547,級別 16,狀態 0,第 1 行
ALTER TABLE 語句與 CHECK 約束"CN_AccountAge18"沖突。該沖突發生於數據庫"Nx",表"dbo.Account", column 'Account_Age'。

  這時候我們換一種方式去執行:

alter table Account
    WITH NOCHECK
    add constraint CN_AccountAge18
    check  (Account_Age > 18);    -- 插入年齡必須大於18

  以上代碼就能夠成功執行,並且隻有以後添加的數據具備約束,之前添加的不符合條件的數據記錄依然存在。

8.2、臨時禁用已存在的約束

當我們需要從另一數據庫中導入數據到表中,而表中已建立瞭約束的時候,可能會存在一些數據和規則不匹配。當然有一個解決方式是先刪除約束,添加需要的數據,然後WITH NOCHECK再添加回去。但是這樣做太麻煩瞭。我們不需要這麼做。我們可以采用名為NOCHECK的選項來運行ALTER語句,這樣就能夠取消需要的約束。

先來看看上節中創建的這個約束:

alter table Account
    add constraint CN_AccountAge18
    check   (Account_Age > 18);    -- 插入年齡必須大於18

要取消以上約束可以這樣來:

Alter Table Account
    NOCHECK
    constraint CN_AccountAge18

執行命令:

insert into Account values (25,'取消瞭約束',17)

執行成功,成功添加瞭一行數據。

留意到又能夠向表中插入格式不匹配的數據瞭。

這裡要說明下,如何知道一個約束是否是啟用還是禁用呢?sp_helpconstraint命令,當我們執行sp_helpconstraint的時候,會有一列status_enabled顯示該約束的啟用狀態:

sp_helpconstraint Account

留意到status_enabled列為Disabled說明是禁用的意思。

當要啟用約束時,隻需要用將語句中的NO CHECK替換為CHECK就可以瞭:

Alter Table Account
    CHECK
    constraint CN_AccountAge18

執行之後,約束又啟用瞭:再來sp_helpconstraint看下:

留意到status_enabled列變成瞭Enabled。

status_enabled的兩種狀態如下:

Enabled:啟用;

Disabled:禁用;

九、規則和默認值(已淘汰)

規則和默認值的應用要早於CHECK和DEFAULT約束。他們是較老的SQL Server備用約束的一部分,當然也不是沒有優點。

自7.0版本之後,MicroSoft指出規則和默認值隻是為瞭向後兼容,而不準備在以後繼續支持這個特性。因此對於生成新代碼時,應該使用約束。

規則、默認值與約束的本質區別是:約束是一個表的特征,本身沒有存在形式,而規則和默認值是表和自身的實際對象,本身存在。約束是在表定義中定義的,而規則和默認值是單獨定義,然後"綁定到"表上。

規則和默認值的獨立對象特性使得它們可以在重用時不用重新定義。實際上,規則和默認值不限於被綁定到表上,它們也可以綁定到數據類型上。

9.1、規則

  規則和CHECK約束非常相似。它們之間的唯一區別是規則每次隻能作用於一個列。可以將同一規則分別綁定到一個表中的多個列,但是規則分別作用於每個列,根本不會意識到其他列的存在。像QtyShipped

<= QtyOrdered這樣的約束不適用於規則(它引用多個列),而LIKE([0-9][0-9][0-9])這樣的定義適用於規則。

定義規則:

下面定義一個規則,這樣就可以首先看到區別所在:

CREATE RULE Age18Rule
    AS @Age > 18

這裡比較的是一個變量,不管被檢查的列是什麼值,這個值將用於替換@Age。因此在這個示例中,規則所綁定的任何列的值都必須大於18。

到目前為止,隻是創建瞭一個規則,但這個規則還沒對任何表的任何列起作用,要激活這個規則需要使用一個存儲過程:sp_bindrule。

將規則Age18綁定到表person的person_age列:

EXEC sp_bindrule 'Age18Rule','person.person_age';

此時,如果我們執行不滿足規則的插入操作:

insert into person values ('綁定規則',17)

將返回如下報錯信息:

消息 513,級別 16,狀態 0,第 1 行
列的插入或更新與先前的 CREATE RULE 語句所指定的規則發生沖突。該語句已終止。沖突發生於數據庫 'Nx',表 'dbo.person',列 'person_age'。
語句已終止。

很明顯,規則已經生效。

要特別註意的是,在綁定之前,規則與任何表,任何列都沒有關系,因此在綁定的時候,第二個參數要加.指定表名與列名(tablename.column)。

解除綁定規則:

當我們需要在一個列上解除綁定規則的時候,隻要執行sp_unbindrule

刪除剛才綁定的規則:

EXEC sp_unbindrule 'person.person_age';

這時候,執行剛才的插入操作,就不會報錯瞭。

刪除規則:

如果希望將規則從數據庫中徹底刪除,那麼可以在表中使用非常熟悉的DROP語法。

DROP RULE <rule name>

如刪除剛才創建的那條規則:

DROP RULE Age18Rule

9.2、默認值

默認值類似於DEFAULT。實際上默認值-DEFAULT約束的關系與規則-CHECK約束的關系差不多。區別在於它們被追加到表中的方式和對用戶自定義數據類型的默認值(是對象,而不是約束)支持。

定義默認值的語法和定義規則類似:

CREATE DEFAULT <default_name>
AS <default value>

創建默認值:

因此,假設要為Age定義一個值為0的默認值:

CREATE DEFAULT AgeDefault
AS 0

綁定默認值:

同樣,如果不綁定到一個對象上,則默認值是不起作用的。要綁定的話,使用存儲過程sp_bindefault。

EXEC sp_bindefault 'AgeDefault','person.person_age';

要從表中解決默認值的綁定,使用sp_unbindefault:

sp_unbindefault 'person.person_age';

刪除默認值:

如果要從數據庫中徹底刪除一個默認值,則可以使用DROP語法,與刪除規則相同:

DROP DEFAULT AgeDefault

9.3確定哪個表和數據類型使用給定的規則或默認值

如果希望刪除或者修改規則或默認值。那麼您可以先看看哪些表和數據類型在使用它們。SQL Server還是采用系統存儲過程解決這個問題。這個存儲過程是sp_depends。其語法如下所示:

EXEC sp_depends <object name>

sp_depends提供瞭依賴於你所查詢對象的所有對象列表。

十、系統視圖

--CHECK約束,數據來源sys.objects.type='C',
--兼容性視圖SYSCONSTRAINTS
select *, ( select c.name from syscolumns c where c.colid = t.parent_column_id and c.id = object_id('Mould')) as 列名
from   sys.check_constraints t
where  parent_object_id = object_id('Mould');

--默認約束,數據來源sys.objects.type=D,
--兼容性視圖SYSCONSTRAINTS
select *, ( select c.name from syscolumns c where c.colid = t.parent_column_id and c.id = object_id('Mould')) as 列名
from   sys.default_constraints t
where  parent_object_id = object_id('Mould');

--主鍵或唯一約束,數據來源sys.objects.type PK 和UQ,
--兼容性視圖SYSCONSTRAINTS
select * from sys.key_constraints t where parent_object_id = object_id('Mould');

--外鍵,數據來源sys.object.type=F,
--兼容性視圖SYSREFERENCES
select * from sys.foreign_keys t where parent_object_id = object_id('Mould');

--根據表名和列名查詢列上的約束
select sysobjects.name, sysobjects.xtype, sysobjects.id
from   sysobjects
       join sysconstraints on sysobjects.id = sysconstraints.constid
where  sysobjects.parent_obj = object_id('Mould') and sysconstraints.colid in ( select colid from syscolumns where id = object_id('Mould')  AND name like  '%' );

到此這篇關於SQL Server約束(constraints)的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: