SQLServer - 約束

一、約束的分類

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

  1、實體約束

    實體約束是關於行的,比如某一行出現的值就不允許出現在其他行,例如主鍵。

  2、域約束

    域約束是關於列的,對於所有行,某一列有那些約束,例如CHECK約束。

  3、參照完整性約束

    如果某列的值必須與其他列的值匹配,那就意味着需要一個參照完整性約束,例如外鍵。

二、約束命名

  在學習約束之前,首先來了解下爲約束命名需要注意哪些地方。

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

  例如,系統生成的主鍵名稱可能是這樣的: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這樣的短語來命名。

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

  1、一致性

  2、通俗易懂

  3、滿足以上兩個條件的情況下簡化名稱。

三、鍵約束

1、主鍵約束

   主鍵是每行的唯一標識符,僅僅通過它就能準確定位到一行,其中主鍵列在整個表中不能有重複,必須包含唯一的值(不能爲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名稱告訴SQLServer如下信息:

  1、添加了一些內容到表中(也可以刪除表中的某些內容)

  2、添加了什麼內容(一個約束)

  3、對約束的命名(允許以後直接訪問約束)

  4、約束的類型(主鍵約束)

  5、約束應用於哪個列。

  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 
) 
複製代碼

2、外鍵約束

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

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

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

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

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

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

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

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

  3、級聯動作

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

  這種進行自動刪除和自動更新的過程稱爲級聯。這種過程,特別是刪除過程,可以經過幾層的以來關係(一條記錄依賴於另一條記錄,而這另一條記錄又依賴其他記錄)。在SQLServer中實現級聯動作需要做的就是修改外鍵語法-只需要在添加前面加上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。後兩個是在SQLServer2005中引入的,如果要兼容到SQLServer2000的話,要避免使用這兩個級聯動作。但是他們的才做是非常簡單的:如果執行更新而改變了一個父行的值,那麼子行的值將被設置爲NULL,或者設置爲該列的默認值(不管SET NULL還是SET DEFAULT)。

  4、外鍵其他方面的考慮

    外鍵中的之只有相中可能的選擇:

    1、在列中填充與被引用表中的相應列相匹配的值。

      通過定義引用列爲NOT NULL,可以使外鍵完全是必須的(即用戶添加數據時必須引用表中必須有相匹配的一行數據)。

    2、不填充任何值,而使該值爲NULL。

      允許引用列有NULL值時,用戶可以選擇不提供值-即使在被引用表沒有與NULL值匹配的行,還是允許插入。

3、唯一約束

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

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

  和主鍵不同,唯一約束不會自動防止設置一個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關鍵字並且當前表中還沒有聚集索引。
  •     每個表中只能有一個主鍵,但可以由多個唯一約束。

4、CHECK約束  

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

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

目標 SQL
限制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'
語句已終止。

5、DEFAULT約束

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

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

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

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

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

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

  5.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)

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

  

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

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

6、禁用約束

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

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

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

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

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

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

  6.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

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

   6.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:禁用;

7、規則和默認值

  規則和默認值的應用要早於CHECK和DEFAULT約束。他們是較老的SQL Server備用約束的一部分,當然也不是沒有優點。自7.0版本之後,MicroSoft列出規則和默認值只是爲了向後兼容,而不準備在以後繼續支持這個特性。因此對於生成新代碼時,應該使用約束。

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

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

  7.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

  7.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

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

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

  EXEC sp_depends <object name>

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

8、觸發器

  觸發器也能夠用於實現數據完整性,這個內容比較多,新建一篇文章敘述。

9、如何選擇

  經過以上的學習,對於數據完整性,你會發現有很多種可以選擇,那麼如何挑選合適的約束呢?

限制 優點 缺點
約束

快速

可以引用其他列

在命令執行前發生

遵循ANSI標準

必須對每個表重新定義

不能引用其他表

不能綁定到數據類型

規則

獨立的對象

可重用

可以綁定到數據類型

命令執行前發生

稍慢

不能跨列使用

不能引用其他表

實際上只用於向後兼容

默認值

非常靈活

可以引用其他列或其他表

可以通過.NET引用SQL Server之外的其他信息

在命令執行之後發生

系統開銷很大

  如果要實現更健壯的邏輯模型以及廣泛使用用戶自定義數據類型,則一般使用規則和默認值。在這種情況下規則和默認值可以提供很多功能,容易管理,而不用太多的編程開銷。

  只有在不能選擇約束時使用觸發器。和約束一樣,他們被附加到表中,而且必須對創建的每個表重新定義。好的方面是觸發器幾乎可以做數據完整性方面的任何操作。實際上再沒有出現外鍵時,他們常被用作外鍵的替代品。

  而在其他情況下,應將約束作爲數據完整性解決方案的選擇。它們執行速度快,而且不難創建。他們的缺點是功能有限(除了外鍵約束,都不能引用其他表),而且對於通用約束邏輯來說,需要一次次地重新定義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章