DbCommand.ExecuteScalar 方法
MSDN 對 DbCommand.ExecuteScalar 方法是這樣描述的:
執行查詢,並返回查詢所返回的結果集中第一行的第一列。 所有其他的列和行將被忽略。 語法: public abstract Object ExecuteScalar() 返回值: 類型: System.Object,結果集中第一行的第一列。 備注: 使用 ExecuteScalar 方法從數據庫中檢索單個值(例如一個聚合值)。 與使用 ExecuteReader 方法然后使用 DbDataReader 返回的數據執行生成單個值所需的操作相比,此操作需要的代碼較少。如果找不到結果集中第一行的第一列;則返回 null 引用(在 Visual Basic 中為 Nothing)。 如果數據庫中的該值為 null,此查詢將返回 DBNull.Value。
准備測試用例
讓我們這實際測試一下吧,首先准備好以下 create-table-Keywords.sql :
1 CREATE TABLE Keywords ( 2 keyword_id SERIAL PRIMARY KEY, 3 keyword VARCHAR(40) NOT NULL, 4 UNIQUE KEY (keyword) 5 )
上面的 SQL 語句來源於《SQL反模式》一書 第17章 可憐人的搜索引擎 第5節 解決方案:使用正確的工具 (第159頁)。
然后在 openSUSE 12.1 操作系統的 MySQL 5.5.16 數據庫中執行以下 SQL 命令:
ben@vbox:~/work/SQL-Antipatterns> mysql -u test -ppwd-for-test test mysql> source create-table-Keywords.sql; Query OK, 0 rows affected (0.18 sec) mysql> desc Keywords; +------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------------+------+-----+---------+----------------+ | keyword_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | keyword | varchar(40) | NO | UNI | NULL | | +------------+---------------------+------+-----+---------+----------------+ 2 rows in set (0.01 sec) mysql> insert into Keywords (keyword) values ('crash'); Query OK, 1 row affected (0.10 sec)
在 MySQL Client 中測試
接着在 MySQL Client 中執行以下三個 SQL select 語句:
mysql> select keyword_id from Keywords where keyword = 'crash'; +------------+ | keyword_id | +------------+ | 1 | +------------+ 1 row in set (0.01 sec) mysql> select keyword_id from Keywords where keyword = 'aborted'; Empty set (0.00 sec) mysql> select max(keyword_id) from Keywords where keyword = 'aborted'; +-----------------+ | max(keyword_id) | +-----------------+ | NULL | +-----------------+ 1 row in set (0.01 sec) mysql>
第一個 select 語句返回一個確實存在的 keyword_id 值,第二個 select 語句返回空結果集,第三個 select 語句返回的結果集中有一行數據,但是其值是 NULL,這是因為 SQL MAX() 函數在起作用。注意由於 Keywords 表的 keyword 列有 unique 索引,上面三個 select 語句返回的結果集中最多只能有一行,不可能有多行。
在 ADO.NET 中測試
我們使用以下 C# 程序 Tester.cs 來測試上述三個 SQL select 語句:
1 using System; 2 using MySql.Data.MySqlClient; 3 4 namespace Skyiv.Test 5 { 6 static class Tester 7 { 8 static void Main() 9 { 10 Test("SELECT keyword_id FROM Keywords WHERE keyword = 'crash'"); 11 Test("SELECT keyword_id FROM Keywords WHERE keyword = 'aborted'"); 12 Test("SELECT MAX(keyword_id) FROM Keywords WHERE keyword = 'aborted'"); 13 } 14 15 static void Test(string sql) 16 { 17 using (var conn = new MySqlConnection("server=localhost;user=test;password=pwd-for-test;database=test")) 18 using (var comm = conn.CreateCommand()) 19 { 20 conn.Open(); 21 comm.CommandText = sql; 22 var result = comm.ExecuteScalar(); 23 Console.WriteLine("Type:{0,-13} DBNull:{1,-5} null:{2,-5} Value:[{3}]", 24 (result == null) ? "(null)" : result.GetType().ToString(), 25 result is DBNull, result == null, result); 26 } 27 } 28 } 29 }
使用 Mono 2.10.6 編譯和運行,結果如下所示:
ben@vbox:~/work/SQL-Antipatterns> dmcs Tester.cs -r:/home/ben/repo/dll/MySql.Data.dll && mono Tester.exe Type:System.UInt64 DBNull:False null:False Value:[1] Type:(null) DBNull:False null:True Value:[] Type:System.DBNull DBNull:True null:False Value:[] ben@vbox:~/work/SQL-Antipatterns>
從上述運行結果中,我們可以看出:
- 對於第二個 select 語句,DbCommand.ExecuteScalar 方法的返回值是 null,調用者需要使用 result == null 來判斷。
- 對於第三個 select 語句,DbCommand.ExceuteScalar 方法的返回值是 DBNull.Value,調用者需要用 result is DBNull 或者 result == DBNull.Value 來判斷。
對 ADO.NET 的 DbCommand.ExecuteScalar 方法的調用者來說,select keyword_id 和 select MAX(keyword_id) 都是一樣方便的,只不過要注意根據所使用的 SQL 語句來選擇使用 result == null 還是使用 result is DBNull 來判斷查詢結果是否為空。所以我建議使用 select keyword_id 這種 SQL 語句,以減少一個 SQL MAX() 調用,提高運行效率。
當然,如果使用 result == null || result is DBNull 來判斷查詢結果是否為空就更保險了,能夠適應這兩種 SQL 語句的寫法,但是運行效率就稍微低了一點。
如果是使用 DbCommand.ExcuteReader 方法來獲得查詢結果:
- 對於第二個 select 語句,需要判斷 DbDataReader.Read 方法的返回值來決定查詢結果是否為空。
- 對於第三個 select 語句,DbDataReader.Read 方法的返回值總是 true,而是通過 DbDataReader.IsDBNull 方法來判斷查詢結果是否為空。
當然,對於查詢結果最多只有一行一列的情況是不推薦使用 DbCommand.ExcuteReader 方法的。只有查詢結果有可能有多行,或者查詢結果有多列的情況下,才需要使用 DbCommand.ExcuteReader 方法。
《SQL反模式》中的用法
在《SQL反模式》一書第159頁是在以下存儲過程中使用 MAX() 函數的:
1 CREATE PROCEDURE BugsSearch(keyword VARCHAR(40)) 2 BEGIN 3 SET @keyword = keyword; 4 PREPARE s1 FROM 'SELECT MAX(keyword_id) INTO @k FROM Keywords WHERE keyword = ?'; 5 EXECUTE s1 USING @keyword; 6 DEALLOCATE PREPARE S1; 7 IF (@k IS NULL) THEN 8 -- (這里省略若干語句) 9 END IF; 10 -- (這里再次省略若干語句) 11 END
看來這里的 MAX() 函數是不能省略的,雖然 Keyswords 表的 keyword 列上有 unique 索引,查詢結果中不可能有多行,但是查詢結果可能為空集,所以需要使用 MAX() 函數將空的查詢結果轉換為值為 NULL 的有一行的查詢結果。