使用檢查約束驗證SQL Server中的數據
原創【51CTO獨家特稿】有許多校驗數據和業務規則是否匹配的方法,在應用程序代碼中可以強制實施數據校驗,也可以由數據庫引擎執行數據校驗。根據校驗的方法不同,規則需求將確定你的應用程序該如何正確地以及該在哪里校驗數據,本文將向你介紹如何使用數據庫“檢查約束”校驗SQLServer中的數據。
什么是檢查約束?
檢查約束是一個識別SQLServer表中每行可接受的列值的規則,檢查約束幫助實施域的完整性,域完整性定義了數據庫表中列的有效值,檢查
約束可以驗證單列的域完整性,也可以驗證多列的域完整性,在單個列上可以有多個檢查約束,如果插入或更新的數據違反了檢查約束,數據
庫引擎將暫時停止INSERT和UPDATE操作。
檢查約束由邏輯表達式構成,邏輯表達式可能是單個表達式,如“Salary<200000.00”,也可能是多個表達式,如“RentalDate>GETDATE
()andRentalDate 中的數據,檢查約束是基于列的,因此,即便表中某列的檢查約束沒有通過,也不會影響到表中其它列的INSERT和UPDATE操作,檢查約束可以在列級創建,也可以在表級創建。 在CREATETABLE語句中創建檢查約束 創建檢查約束的一個方法就是在創建表的時候創建,下面是一個簡單的CREATETABLE腳本,它包含了創建一個檢查約束的代碼: (
IDintPRIMARYKEY,
PositionIDINT,
SalaryTypenvarchar(10),
Salarydecimal(9,2)
CHECK(Salary<150000.00)
); 這里的CHECK子句關聯了Salary列,這是一個列級的約束,如果你創建了一個列級約束,你只能在檢查約束的邏輯表達式中使用列名,這里的檢查約束列就只允許Salary列的值小于150000。創建這個表時也會創建CHECK約束,約束名由系統自動生成,如果你想在CREATETABLE操作時命名你的檢查約束,代碼就可以變成下面這樣: (
IDintPRIMARYKEY,
PositionIDINT,
SalaryTypenvarchar(10),
Salarydecimal(9,2)
CONSTRAINTCK_Payroll_SalaryCHECK(Salary<150000.00)
); 這里我將檢查約束命名為CK_Payroll_Salary了。 上面的例子都僅在單個列上創建了檢查約束,而且也只有一個條件,其實檢查約束表達式可以包括多個條件,下面就是一個包含多個條件的檢查約束: (
IDintPRIMARYKEY,
PositionIDINT,
SalaryTypenvarchar(10),
Salarydecimal(9,2)
CONSTRAINTCK_Payroll_Salary
CHECK(Salary>10.00andSalary<150000.00) ); 如果要讓SQLServer拒絕一條記錄,那在檢查約束邏輯表達式的最終輸出中需要計算為FALSE,因此,在這個例子中,檢查約束會驗證Salary大于10且小于150000,這兩個條件中任意一個檢查結果返回FLASE,都會直接拒絕Payroll表中對行的INSERT或UPDATE請求,提示也會顯示一條錯誤消息。 如果你想創建一個表級檢查約束,你可以使用下面的代碼: (
IDintPRIMARYKEY,
PositionIDINT,
Salarydecimal(9,2),
SalaryTypenvarchar(10), CHECK(Salary>10.00andSalary<150000.00)
); 在這里我創建了單個表級約束,檢查Salary列,但可以使用表中的任意列,因為這是一個表級檢查,注意CHECK子句將會引起SQLServer生成一個檢查約束名,因為我沒有手動為其命名。 在現有表上創建檢查約束 有時,在你設計和創建好表后,你可能想要在表上放一個檢查約束,你可以使用ALTERTABLE語句來實現,下面是一個例子: WITHNOCHECKADDCONSTRAINTCK_Payroll_SalaryType CHECK(SalaryTypein('Hourly','Monthly','Annual')); 我在這里的創建檢查約束將會檢查Payroll表中SalaryType列的值為“Hourly”,“Monthly”或“Annual”的所有記錄,我還給這個檢查約束起了一個名字,叫做“CK_Payroll_SalaryType”。 你也可以在一個ALTERTABLE語句中給表添加多個檢查約束,下面就是這樣一個例子: WITHNOCHECKADDCONSTRAINTCK_Payroll_SalaryType
CHECK(SalaryTypein('Hourly','Monthly','Annual')),
CONSTRAINTCK_Payroll_Salary
CHECK(Salary>10.00andSalary<150000.00);
在這里我在一條ADDCONSTRAINT子句中為SalaryType和Salary這兩列同時增加了檢查約束。 創建多列約束 不用在每個列上都創建約束,相反,可以在多個列上同時創建一個約束來檢查這些列的值,例如,如果我想創建單個約束來檢查Salary和SalaryType,那我可能使用如下的代碼: ADDCONSTRAINTCK_Payroll_Salary_N_SalaryType
CHECK(SalaryTypein('Hourly','Monthly','Annual')
andSalary>10.00andSalary<150000.00); 這個約束和前面的兩個約束完成的事情是一樣的,但你要記住,這樣做有一個不好的后果,那就是最終在理解究竟是SalaryType列,還是Salary列,或者這兩列違反了你的檢查約束時可能比較困難。 這樣做之后還有一個讓人煩惱的是不止使用一列來判斷某個特定列的值是否有效,例如,假設我想要確保在輸入HourlySalaryType時,我想要Salary小于100,或者在輸入MonthlySalaryType時,Salary小于10000,當輸入AnnualSalaryType時,Salary合計是準確的。為了實現這個約束條件,我使用下面的ADDCONSTRAINT子句: ADDCONSTRAINTCK_Payroll_SalaryType_Based_On_Salary
CHECK((SalaryType='Hourly'andSalary<100.00)or
(SalaryType='Monthly'andSalary<10000.00)or
(SalaryType='Annual')); 在這里我將多列條件集中在一起了,又用or條件將它們進行分離,這樣我的檢查約束就可以驗證每個不同的SalaryType的Salary值了。 理解遇到空值時會發生什么 回顧一下我在本文的第一小節“什么是檢查約束”中講到的“當檢查約束的條件表達式返回一個FALSE值時,記錄不能INSERT和UPDATE”,因為如此,空值可能讓進入數據庫的數據并不符合你的要求。例如,假設在payroll表上只有一個CK_Paryroll_SalaryType檢查約束,只需要刷新檢查約束所在內存即可: WITHNOCHECKADDCONSTRAINTCK_Payroll_SalaryType
CHECK(SalaryTypein('Hourly','Monthly','Annual')); 現在,如果你執行下面的INSERT語句: INSERTINTOdbo.Payrollvalues(2,2,NULL,25.00);
INSERTINTOdbo.Payrollvalues(3,3,'Horly',25.00); 你認為將會發生什么?只有第一條INSERT語句會起作用嗎?第二條和第三條INSERT將會怎么樣?它們都會違反CK_Payroll_SalaryType嗎?結果是只有第三條INSERT語句會失敗,它之所以失敗是因為SalaryType被打亂了,不再僅僅是“Hourly”,“Monthly”或“Annual”了,那為什么第二個INSERT語句沒有返回FALSE呢?很明顯,NULL(空值)也不是有效的SalaryType,第二條INSERT語句能夠工作的原因是它運行時CK_Payroll_SalaryType約束沒有返回FALSE值,所以數據庫引擎就插入了第二條記錄。 為什么會這樣呢?因為NULL(空值)在比較操作中時會返回UNKNOWN,因為UNKNOWN并不等價于FLASE,故沒有違反檢查約束。因此,你在寫檢查約束時要當心,你可能想要排除掉包含NULL(空值)的值。上面的例子如果想要排除掉NULL(空值),那代碼要做如下的改動: WITHNOCHECKADDCONSTRAINTCK_Payroll_SalaryType CHECK((SalaryTypein('Hourly','Monthly','Annual'))
andSalaryTypeisnotNULL); 另一個選擇是將SalaryType列設置為一個NOTNULL字段,這樣之后就不用再創建一個檢查約束了,但你會獲得一個不能向表中插入NULL值的錯誤消息。 通過檢查約束進行數據驗證 使用檢查約束后,可以確保你的數據庫只包括通過了檢查的數據,這樣允許你讓數據庫引擎控制你的數據有效性,這樣做之后,你的應用程序就不用再進行數據驗證了,否則程序代碼中到處都穿插有數據校驗的腳本,通過這種方法使數據驗證工作更輕松,更簡潔。CREATETABLEdbo.Payroll
CREATETABLEdbo.Payroll
CREATETABLEdbo.Payroll
CREATETABLEdbo.Payroll
ALTERTABLEdbo.Payroll
ALTERTABLEdbo.Payroll
ALTERTABLEdbo.PayrollWITHNOCHECK
ALTERTABLEdbo.PayrollWITHNOCHECK
ALTERTABLEdbo.Payroll
INSERTINTOdbo.Payrollvalues(1,1,'Hourly',25.00);
ALTERTABLEdbo.Payroll