T-SQL開發

來源:互聯網
上載者:User

之所以把約束和索引放到一起來看,主要是因為主鍵約束和唯一鍵約束,它們會自動建立一個對應的索引,先分別看下資料庫中的幾個約束。

一 約束

在關係型資料庫裡,通常有5種約束,樣本如下:

use tempdbgocreate table s(sid     varchar(20),sname   varchar(20),ssex    varchar(2)  check(ssex='男' or ssex='女') default '男',sage    int         check(sage between 0 and 100),sclass  varchar(20) unique,constraint PK_s primary key (sid,sclass))create table t(teacher  varchar(20) primary key,sid      varchar(20) not null,sclass   varchar(20) not null,num      int,foreign key(sid,sclass) references s(sid,sclass))


單獨定義在某一列上的約束被稱為資料行層級條件約束,定義在多列上的約束則稱為表級約束。


1.主鍵約束

在表中的一列或者多列上,定義主鍵來唯一標識表中的資料行,也就是資料庫設計3範式裡的第2範式;


主鍵約束要求索引值唯一且不可為空:primary key = unique constraint + not null constraint


2.唯一鍵約束

唯一約束和主鍵約束的區別就是:允許NULL,SQL Server 中唯一鍵列,僅可以有一行為NULL,ORACLE中可以有多行列值為NULL。


一個表只能有一個主鍵,但可以有多個唯一鍵:unique index = unique constraint


在一個允許為NULL的列上,想要保證非NULL值的唯一性,該怎麼辦?

從SQL Server 2008開始,可以用篩選索引(filtered index)

use tempdbGOcreate table tb5(id int null)create unique nonclustered index un_ix_01on tb5(id)where id is not nullGO


3.外鍵約束

表中的一列或者多列,引用其他表的主鍵或者唯一鍵。外鍵定義如下:

use tempdbGO--drop table tb1,tb2create table tb1(col1 int Primary key,col2 int)insert into tb1 values (2,2),(3,2),(4,2),(5,2)GOcreate table tb2(col3 int primary key,col4 int constraint FK_tb2 foreign key  references tb1(col1))GOselect * from tb1select * from tb2select object_name(constraint_object_id) constraint_name,       object_name(parent_object_id) parent_object_name,       col_name(parent_object_id,parent_column_id) parent_object_column_name,       object_name(referenced_object_id) referenced_object_name,       col_name(referenced_object_id,referenced_column_id) referenced_object_column_name from sys.foreign_key_columnswhere referenced_object_id = object_id('tb1')


外鍵開發維護過程中,常見的問題及解決方案:

(1) 不能將主表中主鍵/唯一鍵的部分列作為外鍵,必須是全部列一起引用

create table tb3(c1 int,c2 int,c3 int,  constraint PK_tb3 primary key (c1,c2));                                                                                                                                      create table tb4(c4 int constraint FK_tb4 foreign key references tb3(c1),c5 int,c6 int);/*Msg 1776, Level 16, State 0, Line 1There are no primary or candidate keys in the referenced table 'tb3' that match the referencing column list in the foreign key 'FK_tb4'.Msg 1750, Level 16, State 0, Line 1Could not create constraint. See previous errors.*/


(2) 從表插入資料出錯

insert into tb2 values (1,1)/*Msg 547, Level 16, State 0, Line 1The INSERT statement conflicted with the FOREIGN KEY constraint "FK_tb2". The conflict occurred in database "tempdb", table "dbo.tb1", column 'col1'.*/--從表在參照主表中的資料,可以先禁用外鍵(只是暫停約束檢查)alter table tb2 NOCHECK constraint FK_tb2alter table tb2 NOCHECK constraint ALL--從表插入資料後,再啟用外鍵insert into tb2 values (1,1),(3,3),(4,4)alter table tb2 CHECK constraint FK_tb2


(3) 主表刪除/更新資料出錯

--先刪除從表tb2的資料或禁用外鍵,才能刪除主表tb1中的值,否則報錯如下--未被引用的行可被直接刪除insert into tb2 values (2,2)delete from tb1GO/*Msg 547, Level 16, State 0, Line 3The DELETE statement conflicted with the REFERENCE constraint "FK_tb2". The conflict occurred in database "tempdb", table "dbo.tb2", column 'col4'.*/


(4) 清空/刪除主表出錯

--清空主表時,即便禁用外鍵,但外鍵關係依然存在,所以任然無法truncatetruncate table tb1/*Msg 4712, Level 16, State 1, Line 2Cannot truncate table 'tb1' because it is being referenced by a FOREIGN KEY constraint.*/--刪除主表也不行drop table tb1/*Msg 3726, Level 16, State 1, Line 2Could not drop object 'tb1' because it is referenced by a FOREIGN KEY constraint.*/--先truncate從表,再truncate主表也不行truncate table tb2truncate table tb1--唯一的辦法刪掉外鍵,truncate將不受控制alter table tb2 drop constraint FK_tb2truncate table tb1--最後再加上外鍵,注意with nocheck選項,因為主從表裡資料不一致了,所以不檢查約束,否則外鍵加不上alter table tb2 WITH NOCHECKadd constraint FK_tb2 foreign key(col4) references tb1(col1)


最後,雖然一個表上可以建立多個外鍵,但通常出於效能考慮,不推薦使用外鍵,資料參照完整性可以在程式裡完成;


4.CHECK約束

可定義運算式以檢查列值,通常出於效能考慮,不推薦使用。


5.NULL 約束

用於控制列是否允許為NULL。使用NULL時有幾個注意點:

(1) SQL SERVER中彙總函式是會忽略NULL值的;

(2) 字元型的欄位,如果not null,那這個欄位不能為null值,但可以為'',這是空串,和null是不一樣的;

(3) NULL值無法直接參与比較/運算;

declare @c varchar(100)set @c = nullif @c<>'abc' or @c  = 'abc'    print 'null'else    print 'I donot know'GOdeclare @i intset @i = nullprint @i + 1

在開發過程中,NULL會帶來3值邏輯,不推薦使用,對於可能為NULL的值可用預設值等來代替。


6.DEFAULT約束

從系統檢視表來看,default也是被SQL Server當成約束來管理的。

select * from sys.default_constraints


(1) 常量/運算式/純量涵式(系統,自訂、CLR函數)/NULL都可以被設定為預設值;

(2) 利用預設值,向表中添加一個NOT NULL的列,如下:

create table tb6(c1 int not null)insert into tb6 select 1alter table tb6 add c2 int default 35767 not nullselect * from tb6--在alter table完成前,表一直處於鎖定狀態;--如果向大型表添加列,對資料頁的操作需要一些時間,最好事先做好評估。


二 索引

定義約束時,並沒有定義資料庫實現約束的方法,目前的關係型資料庫系統,主鍵和唯一鍵約束藉助唯一索引來實現,所以在建立主鍵/唯一鍵時,都會自動產生一個同名的索引。


那麼由約束產生的唯一索引,和單獨建立的唯一索引有什麼聯絡和區別?


1.建立主鍵或唯一鍵約束時,資料庫自動建立唯一索引

自動產生的該索引是無法刪除的,因為這個索引要用於實現約束,在刪除約束的時候,該索引也被刪除。示範指令碼如下:

--create tableCREATE TABLE TEST_CONS(ID             int,CODE           varchar(100))--insert dataINSERT INTO TEST_CONSSELECT 1,'test1'--add unique constraintALTER TABLE TEST_CONS  ADD CONSTRAINT UQ_TEST_CONS_ID UNIQUE NONCLUSTERED(ID)--retrieve constraintSELECT *  FROM sys.objects WHERE parent_object_id = object_id('TEST_CONS') AND type = 'UQ'--查看約束,返回如下結果:/*name    object_idUQ_TEST_CONS_ID 1243151474*/--retrieve indexSELECT *  FROM sys.indexes WHERE object_id = object_id('TEST_CONS') AND type = 2  --2為非叢集索引--查看約束產生的索引,返回如下結果:/*object_id   name1227151417  UQ_TEST_CONS_ID*/--check constraintINSERT INTO TEST_CONSSELECT 1,'test1'--如果插入重複值提示:UNIQUE KEY 約束,返回如下錯誤:/*訊息,層級,狀態,第行違反了UNIQUE KEY 約束'UQ_TEST_CONS_ID'。不能在對象'dbo.TEST_CONS' 中插入重複鍵。*/ --drop index DROP INDEX UQ_TEST_CONS_ID ON TEST_CONS--如果刪除由約束產生的索引,返回如下錯誤:/*訊息,層級,狀態,第行不允許對索引'TEST_CONS.UQ_TEST_CONS_ID' 顯式地使用DROP INDEX。該索引正用於UNIQUE KEY 約束的強制執行。*/ --drop constraint ALTER TABLE TEST_CONS  DROP CONSTRAINT UQ_TEST_CONS_ID--如果刪除約束,索引也被刪除,以下查詢返回空結果集:--retrieve constraintSELECT *  FROM sys.objects WHERE parent_object_id = object_id('TEST_CONS') AND type = 'UQ'--retrieve indexSELECT *  FROM sys.indexes WHERE object_id = object_id('TEST_CONS') AND type = 2  --2為非叢集索引--drop tableDROP TABLE TEST_CONS


另外,約束產生的索引,有些屬性也是無法被修改的,比如:開關IGNORE_DUP_KEY,唯一的辦法是:先刪除約束,再重新定義約束/索引;單獨定義的索引,則沒有這個限制,如下例:

use tempdbGOcreate table tb_cons(ID int constraint pk_tb_cons primary key)create unique clustered index pk_tb_cons on tb_cons(id) with(DROP_EXISTING = ON, FILLFACTOR = 90)alter index pk_tb_cons on tb_cons rebuild with(IGNORE_DUP_KEY = ON)/*Msg 1979, Level 16, State 1, Line 1Cannot use index option ignore_dup_key to alter index 'pk_tb_cons' as it enforces a primary or unique constraint.*/exec sp_helpindex tb_cons--單獨建立的唯一索引,屬性可以隨意修改create unique index ix_tb_cons on tb_cons(id)alter index ix_tb_cons on tb_cons rebuild with(IGNORE_DUP_KEY = ON, ONLINE = ON)drop table tb_cons


在保證資料唯一性上,唯一索引、唯一約束並沒有區別,那麼應該使用約束還是索引?

約束定義通常出現在資料庫邏輯結構設計階段,即定義表結構時,索引定義通常出現在資料庫物理結構設計/查詢最佳化階段。

從功能上來說唯一約束和唯一索引沒有區別,但在資料庫維護上則不太一樣,對於唯一約束可以用唯一索引代替,以方便維護,但是主鍵約束則沒法代替。


2. 先建立唯一索引,再建立該索引欄位的唯一約束

這時資料庫並不會使用已存在的唯一索引,此時會提示已存在同名索引,約束建立失敗,如果指定不同名的約束,則會產生另個唯一索引。示範指令碼如下:

--create tableCREATE TABLE TEST_CONS(ID             int,CODE           varchar(100))--insert dataINSERT INTO TEST_CONSSELECT 1,'test1'--create indexCREATE UNIQUE INDEX UQ_TEST_CONS_IDON TEST_CONS(ID)--retrieve constraintSELECT *  FROM sys.objects WHERE parent_object_id = object_id('TEST_CONS') AND type = 'UQ'                                                                                             --retrieve indexSELECT *  FROM sys.indexes WHERE object_id = object_id('TEST_CONS') AND type = 2  --2為非叢集索引--check indexINSERT INTO TEST_CONSSELECT 1,'test1'--此時提示為:唯一索引/*訊息2601,層級14,狀態1,第1 行不能在具有唯一索引'UQ_TEST_CONS_ID' 的對象'dbo.TEST_CONS' 中插入重複鍵的行。*/--add constraintALTER TABLE TEST_CONS  ADD CONSTRAINT UQ_TEST_CONS_ID UNIQUE NONCLUSTERED(ID)--此時無法建立與索引同名的唯一約束,因為約束會去產生同名的索引/*訊息1913,層級16,狀態1,第2 行操作失敗,因為在表'TEST_CONS' 上已存在名稱為'UQ_TEST_CONS_ID' 的索引或統計資訊。訊息1750,層級16,狀態0,第2 行無法建立約束。請參閱前面的錯誤訊息。*/--add constraintALTER TABLE TEST_CONS  ADD CONSTRAINT UQ_TEST_CONS_ID_1 UNIQUE NONCLUSTERED(ID)--換個名字當然是可以成功的,但此時又產生了唯一索引UQ_TEST_CONS_ID_1--drop tableDROP TABLE TEST_CONS


3.主鍵是否是叢集索引?

SQL Server預設在定義主鍵時,將產生的唯一索引定義為聚集,剛剛接觸的時候容易被搞混淆了。主鍵對應的索引也可以非聚集,如下:

use tempdbGOcreate table test_pk(id int not null)alter table test_pk add constraint PK_test_pk primary key nonclustered(id);


SQL Server中定義主鍵時,預設產生叢集索引,唯一的好處是主鍵列範圍掃描/尋找的效率比較高,但資料插入效率欠佳(叢集索引,非叢集索引,都得被維護一次),並且主鍵列如果選擇的不好,會影響其他非叢集索引的效能。


ORACLE中定義主鍵時,預設產生非叢集索引,不利於主鍵列的範圍掃描/尋找,但是對於資料插入效率更佳,這是不同資料庫產品各自的權衡。


本文出自 “SQL Server DBA” 部落格,請務必保留此出處http://qianzhang.blog.51cto.com/317608/1333464

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.