1.NULL意思為缺失的值(missing value).
2.三值邏輯(three-valued-logic: TRUE,FALSE,UNKNOWN). 在SQL中有三個邏輯謂詞:TURE,FALSE,UNKNOWN.在大多數的編程語言中只有TRUE和FALSE,而在SQL中獨有UNKNOWN,之所有存在與NULL有關.
比如做如下比較: NULL>32;NULL=NULL;X+NULL>Y;NULL<>NULL.其計算結果均為UNKNOWN.
可能會有些迷惑,於二值邏輯不同(NOT TURE=FALUSE;NOT FALSE=TRUE)的是NOT UNKNOWN=UNKNOWN.
3.UNKNOWN作為FALSE時的處理. 在SQL中查詢過濾時(ON,WHERE,HAVING)會把UNKNOWN作為FALSE處理,這樣就不會把計算值為UNKNOWN的行添加到下一個結果集中.
4.UNKNOWN作為TRUE時的處理. 在CHECK約束中UNKNOWN卻作為TRUE來處理.
5.再談NULL與NULL的比較,上面已經講過(NULL=NULL;NULL<>NULL),即NULL與NULL的比較均為UNKNOWN. 但是對於UNIOUE約束,集合操作(如UNION,EXCEPT),排序,分組時,NULL與NULL為認為是等值的.
關於SQL Server的Null值的比較運算的。一般情況下我們查詢空值或者非空值的時候,用的是is null/is not null,而很少用=/<>。但是在我的這個程序中,沒有用is這樣的關鍵字,而是用=/<>這樣的比較元算符號,這就碰到了一些問題。
問題起源於一個Web查詢頁面,因為問題比較復雜的,所以簡化一下來說明。
在頁面上用戶可以自由選擇數據表的某些字段,填寫該字段的查詢條件,先是選擇比較運算符號(=,<>等),然后填寫值。提交之后,就需要創建一個SQL語句,查詢條件的各部分由不同的程序模塊創建。這里涉及兩個程序模塊,一個模塊根據提交創建比較運算符號,一個模塊負責創建比較值模塊。在創建值模塊中有這樣一個規則,“如果提交的值是空的,把該值設為Null”。
但是我發現,如果比價值為Null的時候,同樣一個SQL查詢語句放在存儲過程里邊查詢和通過應用程序直接查詢的結果是不一樣的。
查了查SQL Server文檔,發現Null值的比較運算,存在兩種規則:
在SQL2000中Null值的比較運算有兩種規則。一種是ANSI SQL(SQL-92)規定的Null值的比較取值結果都為False,既Null=Null取值也是False。另一種不准循ANSI SQL標准,即Null=Null為True。
以一張表T的查詢為例。
表T存在下面的數據:
RowId Data
--------------
1 'test'
2 Null
3 'test1'
按照ANSI SQL標准,下面的兩個查詢都不返回任何行:
Query1: select * from T where Data=null
Query2: select * from T where Data<>null
而按照非ANSI SQL標准,查詢1將返回第二行,查詢2返回1、3行。
ANSI SQL標准中取得Null值的行需要用下面的查詢:
select * from T where Data is null
反之則用is not null。由此可見非ANSI SQL標准中Data=Null等同於Data Is Null,Data<>Null等同於Data Is Not Null。
而控制采用那一種規則,需要使用命令SET ANSI_NULLS [ON/OFF]。ON值采用ANSI SQL標准,OFF值采用非標准模式。另外SET ANSI_DEFAULTS [ON/OFF]命令也可以實現標准的切換,只是這個命令控制的是一組符合SQL-92標准的設置,其中就包括Null值的標准。
默認情況下,數據庫管理程序(DB-Library)是SET ANSI_NULLS為OFF的。但是我們的大多數應用程序,都是通過ODBC或者OLEDB來訪問數據庫的,作為一種開放兼容的數據庫訪問程序,或許是兼容性的考慮,SET ANSI_NULLS值設置為ON。這樣一來帶來的一些問題是需要注意的。像存儲過程或者自定義函數這樣的應用程序都是基於DB-Library的,默認情況下,SET ANSI_NULLS為OFF,並且在這樣的程序中,不能使用SET ANSI_NULLS在一個環境中修改規則,只能修改數據庫配置參數。
考慮下面這種情況。
你的應用程序使用ADODB來訪問數據庫,采用OleDb或者ODBC數據提供程序。對於前面的查詢1:
select * from T where Data=null
你可以直接發送命令取得結果集,也可以把它放到存儲過程當中。但是他們的查詢結果是不一樣的。如果直接使用查詢命令,什么結果也沒有,而如果訪問存儲過程,你獲得第2行的數據。
我寫了一個.Net程序來驗證這一點。同時也為了驗證.Net SqlClient的SET ANSI_NULLS的設置,由於SqlClient不是通過OleDb或者ODBC這些數據提供程序來訪問SQL Server,而是直接對SQL Server進行訪問,本來我以為它會采用SQL Server默認的設置,但是結果恰恰相反,它的默認設置和OleDb、ODBC一樣。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
public class AnsiNullsTest{
public static void Main(String[] args){
IDbConnection conn;
String connType = "SqlClient";
if(args.Length>0)connType = args[0];
if(connType.ToUpper()=="OLEDB"){
Console.WriteLine("Connection Type:OLEDB");
conn = new OleDbConnection("Provider=SQLOLEDB.1;User ID=sa;PWD=test;Initial Catalog=TEST;Data Source=TEST");
}else if(connType.ToUpper()=="ODBC"){
Console.WriteLine("Connection Type:ODBC");
conn = new OdbcConnection("Driver={SQL Server};UID=sa;PWD=test;Database=TEST;Server=TEST");
}else{
Console.WriteLine("Connection Type:SQLClient");
conn = new SqlConnection("Server=TEST;Database=TEST;User ID=sa;PWD=test");
}
Test(conn);
}
public static void Test(IDbConnection conn){
String query1 = "select 'Test' where null=null";
String query2 = "exec p_Test"; //存儲過程中是一樣的SQL語句
IDbCommand cmd;
IDataReader reader;
Console.WriteLine("print 'Test' set ansi_nulls off");
try{
cmd = conn.CreateCommand();
conn.Open();
cmd.CommandText = query1;
reader = cmd.ExecuteReader();
Console.WriteLine("command:" + query1);
while(reader.Read()){
Console.WriteLine("result:" + reader[0].ToString());
}
reader.Close();
cmd.CommandText = query2;
reader = cmd.ExecuteReader();
Console.WriteLine("command:" + query2);
while(reader.Read()){
Console.WriteLine("result:" + reader[0].ToString());
}
reader.Close();
}
catch(Exception ex){
Console.WriteLine(ex.Message);
}
finally{
conn.Close();
}
}
}
它有一個參數,根據參數采用不同的參數值采用不同的數據庫訪問程序。命令對象作了兩次查詢,一次是SQL查詢命令,一次是調用存儲過程。語句都是一樣,但是結果不一樣。