一旦成功地從表中檢索出數據,就需要進一步操縱這些數據,以獲得有用或有意義的結果。這些要求包括:執行計算與數學運算、轉換數據、解析數值、組合值和聚合一個范圍內的值等。
下表給出了T-SQL函數的類別和描述。
函數類別 |
作用 |
聚合函數 |
執行的操作是將多個值合並為一個值。例如 COUNT、SUM、MIN 和 MAX。 |
配置函數 |
是一種標量函數,可返回有關配置設置的信息。 |
轉換函數 |
將值從一種數據類型轉換為另一種。 |
加密函數 |
支持加密、解密、數字簽名和數字簽名驗證。 |
游標函數 |
返回有關游標狀態的信息。 |
日期和時間函數 |
可以更改日期和時間的值。 |
數學函數 |
執行三角、幾何和其他數字運算。 |
元數據函數 |
返回數據庫和數據庫對象的屬性信息。 |
排名函數 |
是一種非確定性函數,可以返回分區中每一行的排名值。 |
行集函數 |
返回可在 Transact-SQL 語句中表引用所在位置使用的行集。 |
安全函數 |
返回有關用戶和角色的信息。 |
字符串函數 |
可更改 char、varchar、nchar、nvarchar、binary 和 varbinary 的值。 |
系統函數 |
對系統級的各種選項和對象進行操作或報告。 |
系統統計函數 |
返回有關 SQL Server 性能的信息。 |
文本和圖像函數 |
可更改 text 和 image 的值。 |
函數的組成
函數的目標是返回一個值。大多數函數都返回一個標量值(scalar value),標量值代表一個數據單元或一個簡單值。實際上,函數可以返回任何數據類型,包括表、游標等可返回完整的多行結果集的類型。本章不准備討論到這個深度,第12章將講解如何創建和使用用戶自定義函數,以返回更復雜的數據。
函數己經存在很長時間了,它的歷史比SQL還要長。在幾乎所有的編程語言中,函數調用的方式都是相同的:
Result=Function()
在T-SQL中,一般用SELECT語句來返回值。如果需要從查詢中返回一個值,就可以把SELECT當成輸出運算符,而不用使用等號:
SELECT Function()
一個論點
對於SQL函數而言,參數表示輸入變量或者值的占位符。函數可以有任意個參數,有些參數是必須的,而有些參數是可選的。可選參數通常被置於以逗號隔開的參數表的末尾,以便於在函數調用中去除不需要的參數。
在SQL Server在線圖書或者在線幫助系統中,函數的可選參數用方括號表示。在下列的CONVERT()函數例子中,數據類型的length和style參數是可選的:
CONVERT (data-type [(length)], expression[,style])
可將它簡化為如下形式,因為現在不討論如何使用數據類型:
CONVERT(date_type, expression[,style])
根據上面的定義,CONVERT()函數可接受2個或3個參數。因此,下列兩個例子都是正確的:
SELECT CONVERT(Varchar(20), GETDATE()) SELECT CONVERT(Varchar(20), GETDATE(), 101) |
這個函數的第一個參數是數據類型Varchar(20),第2個參數是另一個函數GETDATE()。GETDATE()函數用datetime數據類型將返回當前的系統日期和時間。第2條語句中的第3個參數決定了日期的樣式。這個例子中的101指以mm/dd/yyyy格式返回日期。本章后面將詳細介紹GETDATE()函數。即使函數不帶參數或者不需要參數,調用這個函數時也需要寫上一對括號,例如GETDATE()函數。注意在書中使用函數名引用函數時,一定要包含括號,因為這是一種標准形式。
確定性函數
由於數據庫引擎的內部工作機制,SQL Server必須根據所謂的確定性,將函數分成兩個不同的組。這不是一種新時代的信仰,只和能否根據其輸入參數或執行對函數輸出結果進行預測有關。如果函數的輸出只與輸入參數的值相關,而與其他外部因素無關,這個函數就是確定性函數。如果函數的輸出基於環境條件,或者產生隨機或者依賴結果的算法,這個函數就是非確定性的。例如,GETDATE()函數是非確定性函數,因為它不會兩次返回相同的值。為什么要把看起來簡單的事弄得如此復雜呢?主要原因是非確定性函數與全局變量不能在一些數據庫編程對象中使用(如用戶自定義函數)。部分原因是SQL Server緩存與預編譯可執行對象的方式。例如,即席查詢可以使用任何函數,不過如果打算構建先進的、可重用的編程對象,理解這種區別很重要。
以下這些函數是確定性的:
l AVG()(所有的聚合函數都是確定性的)
l CAST()
l CONVERT()
l DATEADD()
l DATEDIFF()
l ASCII()
l CHAR()
l SUBSTRING()
以下這些函數與變量是非確定性的:
l GETDATE()
l @@ERROR
l @@SERVICENAME
l CURSORSTATUS()
l RAND()
在函數中使用用戶變量
變量既可用於輸入,也可用於輸出。在T-SQL中,用戶變量以@符號開頭,用於聲明為特定的數據類型。可以使用SET或者SELECT語句給變量賦值。以下的例子用於將一個int類型的變量@MyNumber傳遞給SQRT()函數:
DECLARE @MyNumber int SET @MyNumber=144 SELECT SQRT(@MyNumber) |
結果是12,即144的平方根。
用SET給變量賦值
以下例子使用另一個int型的變量@MyResult,來捕獲該函數的返回值。這個技術類似於過程式編程語言中的函數調用樣式,即把SET語句和一個表達式結合起來,給參數賦值:
DECLARE @MyNumber int, @MyResult int SET @MyNumber = 144 -- Assign the function result to the variable: SET @MyResult = SQRT(@MyNumber) -- Return the variable value SELECT @MyResult |
用SELECT給變量賦值
使用SELECT的另一種形式也可以獲得同樣的結果。對變量要在賦值前要先聲明。使用SELECT語句來替代SET命令的主要優點是,可以在一個操作內同時給多個變量賦值。執行下面的SELECT語句,通過SELECT語句賦值的變量就可以用於任何操作了。
DECLARE @MyNumber1 int, @MyNumber2 int, @MyResult1 int, @MyResult2 int SELECT @MyNumber1 = 144, @MyNumber2 = 121 -- Assign the function result to the variable: SELECT @MyResult1 = SQRT(@MyNumber1), @MyResult2 = SQRT(@MyNumber2) -- Return the variable value SELECT @MyResult1, @MyResult2 |
上面的例子首先聲明了4個變量,然后用兩個SELECT語句給這些變量賦值,而不是用4個SELECT語句給變量賦值。雖然這些技術在功能上是相同的,但是在服務器的資源耗費上,用一個SELECT語句給多個變量賦值一般比用多個SET命令的效率要高。將一個甚至多個值選進參數的限制是,對變量的賦值不能和數據檢索操作同時進行。這就是上面的例子使用SELECT語句來填充變量,而用另外一個SELECT語句來檢索變量中數據的原因。例如,下面的腳本就不能工作:
DECLARE @RestockName varchar(50) SELECT ProductId ,@RestockName = Name + ':' + ProductNumber FROM Production.Product |
這個腳本會產生如下錯誤:
消息141,級別15,狀態1,第2 行 向變量賦值的SELECT 語句不能與數據檢索操作結合使用。 |
在查詢中使用函數
函數經常和查詢表達式結合使用來修改列值。這只需將列名作為參數傳遞給函數即可,隨后函數將引用插入到SELECT查詢的列的列表中,如下所示:
SELECT Title, NationalIDNumber, YEAR(BirthDate) AS BirthYear FROM HumanResources.Employee |
在這個例子中,BirthDate列的值被作為參數傳遞給YEAR()函數。函數的結果是別名為BirthYear的列。
嵌套函數
我們需要的功能常常不能僅由一個函數來實現。根據設計,函數應盡量簡單,用於提供特定的功能。如果一個函數要執行許多不同的操作,就變得復雜和難以使用。因此,每個函數通常僅執行一個操作,要實現所有的功能,可以將一個函數的返回值傳遞給另一個函數,這稱為嵌套函數調用。
以下是一個簡單的例子:GETDATE()函數的作用是返回當前的日期與時間,但不能返回經過格式化的數據,因為這是CONVERT()函數的功能。要想同時使用這兩個函數,可以把GETDATE()函數的輸出作為CONVERT()函數的輸入參數。
SELECT CONVERT(Varchar(20), GETDATE(), 101) |
聚合函數
報表的典型用途是從全部數據中提取出代表一種趨勢的值或者匯總值,這就是聚合的意義。聚合函數回答數據使用者的如下問題:
上個月雞雛的總銷售量是多少?
19~24歲之間的巴西男性在食品調味品上的平均支出是多少?
上季度所有訂單中從訂購到運輸的最長時間是多少?
收發室里仍在工作的最老的員工是誰?
聚合函數應用特定的聚合操作並返回一個標量值(單一值)。返回的數據類型對應於該列或者傳遞到函數中的值。聚合經常和分組、累積以及透視等表運算一起使用,生成數據分析結果。第7章將詳細介紹這個主題,這里僅討論簡單SELECT查詢中的一些常用函數。
聚合函數不僅可用在SELECT查詢中,還可以和標量輸入值一起使用。那么,這樣做的意義是什么呢?在下列代碼中,將值15傳遞給下列聚合函數,每個函數的返回值都相同:
SELECT AVG(15) SELECT SUM(15) SELECT MIN(15) SELECT MAX(15) |
它們都返回15。雖然,對同一個值求平均、求和、求最小值、求最大值,所得的結果還是那個值。如果對一個值計數,又會產生什么結果呢?
SELECT COUNT(15)
得到的值是1,因為函數只計數了一個值。
現在做一些有意義的事。聚合函數只有在處理結果集合中的一組數據時才有意義。每個函數都處理某列的非空值。除非使用分組操作(詳見第7章),否則不能在同一個SELECT語句中既返回聚合的值,又返回常規的列值。
AVG()函數
AVG()函數用於返回一組數值中所有非空數值的平均值。例如,表6-2包含了體操成績。
表 6-2
體操運動員 |
項 目 |
成 績 |
Sara |
跳馬 |
9.25 |
Cassie |
跳馬 |
8.75 |
Delaney |
跳馬 |
9.25 |
Sammi |
跳馬 |
8.05 |
Erika |
跳馬 |
8.60 |
Sara |
平衡木 |
9.70 |
Cassie |
平衡木 |
9.00 |
Delaney |
平衡木 |
9.25 |
Sammi |
平衡木 |
8.95 |
Erika |
平衡木 |
8.85 |
對這些數據執行以下查詢:
SELECT AVG(Score)
結果是8.965。
如果有三個女孩沒有完成一些項目,在表中沒有記錄成績,則可用NULL來表示(見表6-3)。
表 6-3
體操運動員 |
項 目 |
成 績 |
Sara |
跳馬 |
9.25 |
Cassie |
跳馬 |
8.75 |
Delaney |
跳馬 |
NULL |
Sammi |
跳馬 |
8.05 |
Erika |
跳馬 |
8.60 |
Sara |
平衡木 |
9.70 |
Cassie |
平衡木 |
NULL |
Delaney |
平衡木 |
9.25 |
Sammi |
平衡木 |
NULL |
Erika |
平衡木 |
8.85 |
腳本:
create table #GymEvent(Player varchar(10),[Subject] nvarchar(5),Scoredecimal(4,2)) go insert into #GymEvent values('Sara','跳馬',9.25) insert into #GymEvent values('Cassie','跳馬',8.75) insert into #GymEvent values('Delaney','跳馬',NULL) insert into #GymEvent values('Sammi','跳馬',8.05) insert into #GymEvent values('Erika','跳馬',8.60) insert into #GymEvent values('Sara','平衡木',9.70) insert into #GymEvent values('Cassie','平衡木',NULL) insert into #GymEvent values('Delaney','平衡木',9.25) insert into #GymEvent values('Sammi','平衡木',NULL) insert into #GymEvent values('Erika','平衡木',8.85) go drop table #GymEvent |
在這種情況下,計算平均值時只考慮實際的數值,NULL不參與運算,結果是8.921429。 但是,如果把缺少的成績也算在內,即用數值0代替NULL,則會嚴重影響最終成績(6.245),她們能不能進入國家級的比賽就難說了。
COUNT()函數
COUNT()函數用於返回一個列內所有非空值的個數,這是一個整型值。比如,在上一個例子中,體操數據被保存在#GymEvent表中,要確定Sammi參加的項目數,則可以執行下列查詢:
SELECT COUNT(Score) FROM #GymEvent WHERE Player='Sammi'
結果是1,因為Sammi只參加了跳馬比賽,她的平衡木成績是NULL。
如果需要確定表中的行數,無論這些行是不是NULL值,都可以使用以下語法:
SELECT COUNT (*) FROM #GymEvent
以Sammi為例,COUNT(*)查詢如下所示:
SELECT COUNT(*) FROM #GymEvent WHERE Player='Sammi'
由於COUNT(*)函數會忽略NULL值,所以這個查詢的結果是2。
MIN()與MAX()函數
MIN()函數用於返回一個列范圍內的最小非空值;MAX()函數用於返回最大值。這兩個函數可以用於大多數的數據類型,返回的值根據對不同數據類型的排序規則而定。為了說明這兩個函數,假設有一個表包含了兩列值,一列是整型值,另一列是字符型值,如表6-4所示。
表 6-4
IntegerColumn(int類型) |
VarCharColumn(varChar類型) |
2 |
2 |
4 |
4 |
12 |
12 |
19 |
19 |
腳本:
create table #Temp(IntegerColumn int,VarCharColumn varchar(10)) go insert into #Temp values(2,'2') insert into #Temp values(4,'4') insert into #Temp values(12,'12') insert into #Temp values(19,'19') go drop table #Temp |
如果分別調用MIN()與MAX()函數將會返回什么值呢?
select MIN(IntegerColumn),MAX(IntegerColumn) from #Temp select MIN(VarCharColumn),MAX(VarCharColumn) from #Temp |
因為VarCharColumn中值的存儲類型為字符類型,而不是數字,所以結果以每個字符的ASCII值為順序從左到右排序。這就是12比其他值小、而4比其他值大的原因。
SUM()函數
SUM()函數是最常用的聚合函數之一,它的功能很容易理解:和AVG()函數一樣,它用於數值數據類型,返回一個列范圍內所有非空值的總和。
配置變量
配置變量不是函數,不過它們的用法和系統函數相同。每個全局變量都能夠返回SQL Server執行環境的標量信息。以下是一些常見的例子。
@@ERROR變量
這個變量包含當前連接發生的最后一次錯誤的代碼。在執行的語句沒有錯誤時,@@ERROR變量的值是0。出現標准錯誤時,錯誤是由數據庫引擎引發的。所有的標准錯誤代碼與消息都保存在sys.messages系統視圖中,可以使用如下腳本查詢:
SELECT * FROM sys.messages
定制錯誤可以通過調用RAISERROR語句來手動引發,並調用sp_addmessage系統存儲過程將其添加到sysmessages表中。
以下是一個@@ERROR變量的簡單例子。先試着將一個數除以0,數據庫引擎會引發標准錯誤號為8134的錯誤。注意查看Results選項卡中的查詢結果。在發生錯誤時,Management Studio的Messages選項卡將默認顯示在Results選項卡的上面:
SELECT 5 / 0 SELECT @@ERROR |
在成功檢索@@ERROR的值后,@@ERROR的值將返回0,因為@@ERROR只保存了上次執行的語句的錯誤代碼。如果希望檢索更多的錯誤信息,可以使用如下腳本從sysmessages視圖中得到:
SELECT 5 / 0 SELECT * FROM master.dbo.sysmessages WHERE error = @@ERROR |
本節的后面部分內容將說明如何通過使用錯誤函數來更高效地返回錯誤數據。
除了美國英語之外,SQL Server還默認安裝了其他語言。每種語言專用的錯誤消息都有一個語言標識符(mslangid),對應於syslanguages表中的一種語言,如下圖所示。
error |
severity |
dlevel |
description |
msglangid |
8134 |
16 |
0 |
Divide by zero error encountered. |
1033 |
8134 |
16 |
0 |
Fehler aufgrund einer Division durch Null. |
1031 |
8134 |
16 |
0 |
Division par zéro. |
1036 |
8134 |
16 |
0 |
0 除算エラーが発生しました。 |
1041 |
8134 |
16 |
0 |
Error de división entre cero. |
3082 |
8134 |
16 |
0 |
Errore di divisione per zero. |
1040 |
8134 |
16 |
0 |
Обнаружена ошибка: деление на ноль. |
1049 |
8134 |
16 |
0 |
Erro de divisão por zero. |
1046 |
8134 |
16 |
0 |
發現除以零的錯誤。 |
1028 |
8134 |
16 |
0 |
0으로 나누기 오류가 발생했습니다. |
1042 |
8134 |
16 |
0 |
遇到以零作除數錯誤。 |
2052 |
屬性名mslangid被非正式地定義為Microsoft Global Language Identifier。微軟公司用這個標識符來標識一種語言或語言和國家的組合,微軟公司把語言和國家的組合定義為地區。例如,在隨SQL Server安裝的英語中,美國英語的mslangid是1033,英國英語的mslangid是2057。要檢索出所有已安裝的、支持的語言,可以執行下面的查詢:
SELECT alias, name, msglangid FROM sys.syslanguages |
@@SERVICENAME變量
這個變量是用於執行和維護當前SQL Server實例的Windows服務名。它通常返回SQL Server默認實例MSSQLSERVER,但SQL Server的指定實例有唯一的服務名。例如在名為WoodVista的計算機上有兩個SQL Server實例:默認實例和指定實例AughtEight。如在默認實例上檢索@@SERVICENAME全局變量的內容,將返回MSSQLSERVER,但在指定實例上檢索,會返回AUGHTEIGHT。
@@TOTAL_ERRORS變量
這個變量用於記錄從打開當前連接開始發生的總錯誤次數。和@@ERROR變量一樣,它對每個用戶會話是唯一的,並將在連接關閉時被重置。
@@TOTAL_READ變量
這個變量記錄從打開當前連接時開始計算的磁盤讀取總數。DBA使用這個變量查看磁盤讀取活動的情況。
@@VERSION變量
這個變量包含當前SQL Server實例的完整版本信息。
SELECT @@VERSION
比如,對於運行在Windows 7上的SQL Server 2008開發版實例,以上腳本能夠返回如下信息:
Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) Jul 9 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Enterprise Edition on Windows NT 6.1 <X86> (Build 7600: )
實際的版本號是一個簡單的整型值,它在微軟公司內部使用。而發行的產品可能有其他的商標名。在本例中,SQL Server 2005的版本是9,SQL Server 2008的版本是10。Windows XP Professional顯示為Windows NT 5.l版,而Vista顯示為6.0版。構建號用於內部控制,反映beta版和預覽版以及正式發行后的補丁包的變化。
錯誤函數
前面學習了如何使用@@ERROR全局變量來檢索錯誤信息。而返回所有錯誤數據的更好方法是使用錯誤函數。這些函數返回的信息可以存儲在錯誤跟蹤表中,以供錯誤審核。錯誤函數嵌套在錯誤處理例程中。第11章將詳細討論錯誤處理,其實通過使用嵌套在TRY和END TRY語句中的代碼塊,后跟一個放在CATCH和END CATCH語句中的代碼塊就可以實現錯誤處理。
--Try to do something BEGIN TRY SELECT 5 / 0 END TRY --If it causes an error, do this BEGIN CATCH PRINT ERROR_MESSAGE() END CATCH |
所謂的錯誤捕獲,其實就是這個意思。如果運行上面的示例,將不會出現可識別的錯誤,因為錯誤將被捕獲並在CATCH語句塊中進行處理。在編寫錯誤處理代碼時,SQL程序員必須把這些代碼放在會引發系統錯誤的catch代碼塊中。
下列幾個錯誤函數用於返回錯誤的特定信息:
函數 |
說明 |
ERROR_MESSAGE() |
返回錯誤的描述。 |
ERROR_NUMBER() |
返回錯誤號。 |
ERROR_SEVERITY() |
返回錯誤的嚴重級別。錯誤的嚴重級別是一個從0到25的整數。 |
ERROR_STATE() |
返回錯誤的狀態號。錯誤狀態是一個整數,可以唯一地表示系統錯誤的原因。 |
ERROR_LINE() |
返回例程中導致出錯的行號。 |
ERROR_PROCEDURE() |
返回發生錯誤的存儲過程名或觸發器名。 |
下表簡要描述了嚴重級別。
嚴 重 級 別 |
說 明 |
0~10 |
信息性消息。不會引發系統錯誤 |
11~16 |
用戶可以更正的錯誤,例如違反了外鍵或主鍵規則 |
17 |
非致命的、不重要的資源錯誤 |
18 |
非致命的內部錯誤 |
19 |
致命的、不重要的資源錯誤 |
20 |
當前進程中的致命錯誤 |
21 |
所有進程中的致命數據庫錯誤 |
22 |
致命的表完整性錯誤 |
23 |
致命的數據庫完整性錯誤 |
24 |
致命的硬件錯誤 |
25 |
致命的系統錯誤 |
下面腳本使用T-SQL的內置錯誤處理功能,來捕獲和輸出遇到除0錯誤時返回的錯誤數據。SELECT命令的結果將顯示在Management Studio的消息選項卡中。
--Try to do something BEGIN TRY SELECT 5 / 0 END TRY --If it causes an error, do this BEGIN CATCH SELECT ERROR_MESSAGE(),ERROR_NUMBER(),ERROR_SEVERITY(), ERROR_STATE(),ERROR_LINE(),ERROR_PROCEDURE() END CATCH |
可以看出,執行這個腳本會在消息選項卡中返回有關錯誤的更多詳細信息,而不僅僅是錯誤號本身。
ERROR_PROCEDURE()函數不能返回過程名,因為錯誤是在ad-hoc查詢中生成的。
轉換函數
數據類型轉換可以通過CAST()和CONVERT()函數來實現。大多數情況下,這兩個函數是重疊的,它們反映了SQL語言的演化歷史。這兩個函數的功能相似,不過它們的語法不同。雖然並非所有類型的值都能轉變為其他數據類型,但總的來說,任何可以轉換的值都可以用簡單的函數實現轉換。
CAST()函數
CAST()函數的參數是一個表達式,它包括用AS關鍵字分隔的源值和目標數據類型。以下例子用於將文本字符串'123'轉換為整型:
SELECT CAST('123' AS int)
返回值是整型值123。如果試圖將一個代表小數的字符串轉換為整型值,又會出現什么情況呢?
SELECT CAST('123.4' AS int)
CAST()函數和CONVERT()函數都不能執行四舍五入或截斷操作。由於123.4不能用int數據類型來表示,所以對這個函數調用將產生一個錯誤:
Server: Msg 245, Level 16, State 1, Line 1
Syntax error converting the varchar value
'123.4' to a column of data type int.
在將varchar 值'123.4' 轉換成數據類型int 時失敗。
要返回一個合法的數值,就必須使用能處理這個值的數據類型。對於這個例子,存在多個可用的數據類型。如果通過CAST()函數將這個值轉換為decimal類型,需要首先定義decimal值的精度與小數位數。在本例中,精度與小數位數分別為9與2。精度是總的數字位數,包括小數點左邊和右邊位數的總和。而小數位數是小數點右邊的位數。這表示本例能夠支持的最大的整數值是9999999,而最小的小數是0.01。
SELECT CAST('123.4' AS decimal(9,2))
decimal數據類型在結果網格中將顯示有效小數位:123.40
精度和小數位數的默認值分別是18與0。如果在decimal類型中不提供這兩個值,SQL Server將截斷數字的小數部分,而不會產生錯誤。
SELECT CAST('123.4' AS decimal)
結果是一個整數值:123
在表的數據中轉換數據類型是很簡單的。下面的例子使用Product表,首先執行如下查詢:
SELECT ProductNumber, ProductLine, ProductModelID FROM Production.Product WHERE ProductSubcategoryID < 4 |
假定產品經理已經創建了一個系統,用於唯一地標識生產出來的每輛自行車,以便跟蹤其型號、類型和類別。他決定合並產品號、產品生產線標識符、產品型號標識符和一個順序號,為生產出來的每輛自行車創建一個唯一的序列號。在這個過程的第一步,他要求提供包括除順序號之外的所有屬性的所有可能產品的根標識符。
如果使用下面的表達式,就不能得到希望的結果,如圖6-2所示。
SELECT ProductNumber + '-' + ProductLine + '-' + ProductModelID AS BikeSerialNum FROM Production.Product WHERE ProductSubcategoryID < 4 |
消息245,級別16,狀態1,第1 行 在將nvarchar 值'BK-R93R-62-R -' 轉換成數據類型int 時失敗。 |
我們沒有得到希望的結果,而得到了有點奇怪的錯誤消息:請把nvarchar值轉換為int。因為之前我們沒有要求進行任何轉換,所以這個錯誤很奇怪。這個查詢的問題在於我們試圖利用第一個連接符來連接字符值ProductNumber,利用第二個連接符連接另一個字符值ProductLine,最后連接的是ProductModelID字符值(它是一個整數)。
查詢引擎會把連接符當成一個數學運算符,而不是一個字符。不管結果是什么,都需要更正這個表達式,以確保使用正確的數據類型。以下表達式執行了必要的類型轉換,返回如圖6-3所示的結果:
SELECT ProductNumber + '-' + ProductLine + '-' + CAST(ProductModelID AS char(4)) AS BikeSerialNum FROM Production.Product WHERE ProductSubcategoryID < 4 |
如果把整型值轉換為字符類型就不會增加多余的空格了。查詢引擎將把這些值用加號和連接符組合在一起,進行字符串連接運算,而不是和前面的數值進行加法或者減法運算了。
CONVERT()函數
對於簡單類型轉換,CONVERT()函數和CAST()函數的功能相同,只是語法不同。CAST()函數一般更容易使用,其功能也更簡單。CONVERT()函數的優點是可以格式化日期和數值,它需要兩個參數:第1個是目標數據類型,第2個是源數據。以下的兩個例子和上一節的例子類似:
SELECT CONVERT(int, '123') SELECT CONVERT(decimal(9,2), '123.4') |
CONVERT()函數還具有一些改進的功能,它可以返回經過格式化的字符串值,且可以把日期值格式化成很多形式。有28種預定義的符合各種國際和特殊要求的日期與時間輸出格式。下表列出了這些日期格式。
如果 expression 為 date 或 time 數據類型,則 style 可以為下表中顯示的值之一。其他值作為 0 進行處理。SQL Server 使用科威特算法來支持阿拉伯樣式的日期格式。
yy(1) |
yyyy |
標准 |
輸入/輸出 (3) |
- |
0 或 100 (1, 2) |
默認 |
mon dd yyyy hh:miAM(或 PM) |
1 |
101 |
美國 |
mm/dd/yyyy |
2 |
102 |
ANSI |
yy.mm.dd |
3 |
103 |
英國/法國 |
dd/mm/yyyy |
4 |
104 |
德國 |
dd.mm.yy |
5 |
105 |
意大利 |
dd-mm-yy |
6 |
106 (1) |
- |
dd mon yy |
7 |
107 (1) |
- |
mon dd, yy |
8 |
108 |
- |
hh:mi:ss |
- |
9 或 109 (1, 2) |
默認設置 + 毫秒 |
mon dd yyyy hh:mi:ss:mmmAM(或 PM) |
10 |
110 |
美國 |
mm-dd-yy |
11 |
111 |
日本 |
yy/mm/dd |
12 |
112 |
ISO |
yymmdd yyyymmdd |
- |
13 或 113 (1, 2) |
歐洲默認設置 + 毫秒 |
dd mon yyyy hh:mi:ss:mmm(24h) |
14 |
114 |
- |
hh:mi:ss:mmm(24h) |
- |
20 或 120 (2) |
ODBC 規范 |
yyyy-mm-dd hh:mi:ss(24h) |
- |
21 或 121 (2) |
ODBC 規范(帶毫秒) |
yyyy-mm-dd hh:mi:ss.mmm(24h) |
- |
126 (4) |
ISO8601 |
yyyy-mm-ddThh:mi:ss.mmm(無空格) |
- |
127(6, 7) |
帶時區 Z 的 ISO8601。 |
yyyy-mm-ddThh:mi:ss.mmmZ (無空格) |
- |
130 (1, 2) |
回歷 (5) |
dd mon yyyy hh:mi:ss:mmmAM |
- |
131 (2) |
回歷 (5) |
dd/mm/yy hh:mi:ss:mmmAM |
1. 這些樣式值將返回不確定的結果。包括所有 (yy)(不帶世紀數位)樣式和一部分 (yyyy)(帶世紀數位)樣式。
2. 默認值(style 0 或 100、9 或 109、13 或 113、20 或 120 以及 21 或 121)始終返回世紀數位 (yyyy)。
3. 轉換為 datetime 時輸入;轉換為字符數據時輸出。
4. 為用於 XML 而設計。對於從 datetime 或 smalldatetime 到字符數據的轉換,其輸出格式如上一個表所述。
5. 回歷是有多種變體的日歷系統。SQL Server 使用科威特算法。
a) 默認情況下,SQL Server 基於截止年份 2049 年來解釋兩位數的年份。換言之,就是將兩位數的年份 49 解釋為 2049,將兩位數的年份 50 解釋為 1950。許多客戶端應用程序(如基於自動化對象的應用程序)都使用截止年份 2030 年。SQL Server 提供了“兩位數年份截止”配置選項,可通過此選項更改 SQL Server 使用的截止年份,從而對日期進行一致處理。建議您指定四位數年份。
6. 僅支持從字符數據轉換為 datetime 或 smalldatetime。僅表示日期或時間成分的字符數據轉換為 datetime 或 smalldatetime 數據類型時,未指定的時間成分設置為00:00:00.000,未指定的日期成分設置為 1900-01-01。
7. 使用可選的時間區域指示符 (Z) 更便於將具有時區信息的 XML datetime 值映射到沒有時區的 SQL Server datetime 值。Z 是時區 UTC-0 的指示符。其他時區則以 + 或 -方向的 HH:MM 偏移量來指示。例如:2006-12-12T23:45:12-08:00。
從 smalldatetime 轉換為字符數據時,包含秒或毫秒的樣式將在這些位置上顯示零。使用相應的 char 或 varchar 數據類型長度從 datetime 或 smalldatetime 值轉換時,可截斷不需要的日期部分。
從樣式包含時間的字符數據轉換為 datetimeoffset 時,將在結果末尾追加時區偏移量。
這個函數的第三個參數是可選的,該參數用於接收格式代碼整型值。表中的例子用於對DateTime數據類型進行轉換。在轉換SmallDateTime數據類型時,格式不變,但一些元素會顯示為0,因為該數據類型不支持毫秒。以下的腳本例子將輸出格式化的日期:
SELECT 'Default Date:' + CONVERT(Varchar(50), GETDATE(), 100)
Default Date: Apr 25 2005 1:05PM
SELECT 'US Date:' + CONVERT(Varchar(50), GETDATE(), 101)
US Date: 04/25/2005
SELECT 'ANSI Date:' + CONVERT(Varchar(50), GETDATE(), 103)
ANSI Date: 2005.04.25
SELECT 'UK/French Date:' +CONVERT (Varchar(50), GETDATE(), 103)
UK/French Date: 25/04/2OO5
SELECT 'German Date:' + CONVERT(Varchar(50), GETDATE(), 104)
German Date: 25.04.2005
格式代碼0,1和2也可用於數字類型,它們對小數與千位分隔符格式產生影響。而不同的數據類型所受的影響是不一樣的。一般來說,使用格式代碼0(或者不指定這個參數的值),將返回該數據類型最慣用的格式。使用1或者2通常顯示更為詳細或者更精確的值。以下例子使用格式代碼0:
DECLARE @Num Money SET @Num = 1234.56 SELECT CONVERT(varchar(50), @Num, 0) |
返回結果如下:
1234.56
使用值1則返回如下結果:
1,234.56
使用值2則返回如下結果:
1234.5600
以下例子和上例相同,但是使用Float類型:
DECLARE @Num float SET @Num = 1234.56 SELECT CONVERT(varchar(50), @Num, 2) |
使用值0不會改變所提供的格式,但是使用值1或2將返回以科學計數法表示的數字,后者使用了15位小數:
1.23456000000000e+003
STR()函數
這是一個將數字轉換為字符串的快捷函數。這個函數有3個參數:數值、總長度和小數位數。如果數字的整數位數和小數位數(要加上小數點占用的一個字符)的總和小於總長度,對結果中左邊的字符將用空格填充。在下面第1個例子中,包括小數點在內一共是5個字符。結果顯示在網格中,顯然左邊的空格被填充了。這個調用指定,總長度為8個字符,小數位為4位:
SELECT STR(123.4, 8, 4)
結果值的右邊以0填充:123.4000。
下面給函數傳遞了一個10字符的值,並指定結果包含8個字符,有4個小數位:
SELECT STR(123.456789, 8, 4)
只有將這個結果截斷才能符合要求。STR()函數對最后一位進行四舍五入:123.4568。現在,如果為函數傳遞數字1,並指定結果包含6個字符,有4個小數位,STR()函數將用0補足右邊的空位:
SELECT STR(1, 6, 4)
1.0000
然而,如果指定的總長度大於整數位數、小數點和小數位數之和,結果值的左邊將用空格補齊:
SELECT STR(1, 6, 4)
1.0000
SELECT STR(1, 12, 4)
---------- 1.0000
游標函數與變量
游標可以處理多行數據,在過程循環中一次訪問一行。和基於集合的高效操作相比,這個功能對系統資源的消耗更大。可以用一個函數和兩個全局變量來管理游標操作。
CURSOR_STATUS()函數
這個函數返回一個整型值,表示傳遞給這個函數的游標類型變量的狀態。有很多不同類型的游標會影響這個函數的操作。為簡單起見,下表列出了這個函數的常見返回值。
返 回 值 |
說 明 |
1 |
游標包含一行或多行(動態游標包含0行或者多行) |
0 |
游標不包含行 |
-1 |
游標已關閉 |
-2 |
游標未分配 |
-3 |
游標不存在 |
@@CURSOR_ROWS全局變量
這個變量是一個整型值,表示在當前連接中打開的游標中的行數。根據游標類型,這個值也能不代表結果集中的實際行數。
@@FETCH_STATUS全局變量
這個變量是一個標記,用於表示當前游標指針的狀態。這個變量主要用來判斷某行是否存在,以及在執行了FETCH NEXT語句后,是否已執行到結果集的尾部。打開游標時,@@FETCH_STATUS變量值為-1。一旦把第一個值放在游標中,@@FETCH_STATUS變量值就變成0。當不再把更多的行放在游標中時,該變量的值將變回-1。
日期函數
這些函數可以操作DateTime與SmallDateTime類型的值。有些函數可用於解析日期值的日期與時間部分,有些函數可用於比較、操縱日期/時間值。日期數據類型的區別如下表所示。
數據類型 |
輸出 |
time |
12:35:29. 1234567 |
date |
2007-05-08 |
smalldatetime |
2007-05-08 12:35:00 |
datetime |
2007-05-08 12:35:29.123 |
datetime2 |
2007-05-08 12:35:29. 1234567 |
datetimeoffset |
2007-05-08 12:35:29.1234567 +12:15 |
DATEADD()函數
DATEADD()函數用於在日期/時間值上加上日期單位間隔。比如,要得到2007年4月29日起90天后的日期,可以使用下列語句:
SELECT DATEADD(DAY, 90, '4-29-2007')
結果:2007-07-28 00:00:00.000
可以把下表的值作為時間間隔參數傳遞給DATEADD()函數。
datepart |
縮寫 |
year |
yy, yyyy |
quarter |
qq, q |
month |
mm, m |
dayofyear |
dy, y |
day |
dd, d |
week |
wk, ww |
weekday |
dw, w |
hour |
hh |
minute |
mi, n |
second |
ss, s |
millisecond |
ms |
microsecond |
mcs |
nanosecond |
ns |
在下面列出的例子中,我們使用和上一個例子一樣的日期,並且在這些例子中還包含了時間數據。每個操作的結果將顯示在查詢的下一行中。
18年后:
SELECT DATEADD(YEAR, 18, '4-29-1988 10:30 AM')
2006-04-29 10:30:00.000
18年前:
SELECT DATEADD(YY, -18, '4-29-1988 10:30 AM')
1970-04-29 10:30:00.000
9000秒后:
SELECT DATEADD(SECOND, 9000, '4-29-1988 10:30 AM')
1988-04-29 13:00:00.000
9000000毫秒前:
SELECT DATEADD(MS, -9000000, '4-29-1988 10:30 AM')
1988-04-29 08:00:00.000
可以將CONVERT()函數和DATEADD()函數組合在一起,來對1989年9月8日9個月前的日期值進行格式化。
SELECT CONVERT(varchar(20), DATEADD(M, -9, '9-8-1989'), 101)
12/08/1988
這將返回一個可變長度的字符值,比前面例子結果中的默認日期更易容易理解。這是一個函數嵌套調用,DATEADD()函數的返回值(一個DateTime類型的值)被作為值參數傳遞給CONVERT()函數。
DATEDIFF()函數
DATEADD()和DATEDIFF()函數可以看作一對表兄弟,有點像乘法與除法。在等式的兩端有4個元素:起始日期、時間間隔(datepart)、差值和最終日期。如果已知其中的三個值,就可以求出第4個值。如果在DATEADD()函數中使用起始日期、一個整型值和一個時間間隔,就可返回與起始日期相關的最終日期值。如果提供了起始日期、時間間隔和最終日期,DATEDIFF()函數就可以返回差值。
為了說明這一點,我們選擇任意兩個日期與一個時間間隔作為參數。這個函數將以所提供的時間間隔為單位返回兩個日期之間的差值。要知道1989年9月8日和1991年10月17日之間差了幾個月,可編寫如下查詢代碼:
SELECT DATEDIFF(MONTH, '9-8-1989', '10-17-1991')
結果是25個月。如果以日期為單位呢?
SELECT DATEDIFF(DAY, '9-8-1989', '10-17-1991')
結果是769天。
1996年7月2日和1997年8月4日之間差幾個星期?
SELECT DATEDIFF(WEEK, '7-2-1996', '8-4-1997')
57星期。甚至可以算出自己的年齡是多少秒:
DECLARE @MyBirthDate datetime SET @MyBirthDate = '7-16-1962' SELECT DATEDIFF(SS, @MyBirthDate, GETDATE()) |
結果顯示有些人已經活了15億秒了!
可以將列名作為參數,把這個函數用在查詢中。首先建立一個簡單的表,其中包含一些人的姓名和生日:
SELECT c.FirstName ,c.LastName ,e.BirthDate ,DATEDIFF(YEAR, e.BirthDate, GETDATE()) AS ApproximateAge FROM HumanResources.Employee as e inner join Person.Contact as c on e.ContactID = c.ContactID order by c.LastName |
下圖顯示了結果:
初看起來結果是對的,但存在的問題是年齡值沒有精確到日。比如,根據表中的數據,Nancy的生日是12月21日,他今年將慶祝第32個生日(這個查詢在2010年8月運行)。如果依據上述計算結果來確定他的年齡何時變化,就應在一月份的某天給他發生日卡片,這比實際日期提前了11個月。
除非用更小的時間單位來計算這些日期的差,否則結果只在雇員實際生日的一年以內是精確的。以下例子將用差值除以一年(包括閏年)的天數,並將結果值轉換為int類型,進行取整運算,而不是四舍五入。
SELECT c.FirstName ,c.LastName ,e.BirthDate ,DATEDIFF(YEAR, e.BirthDate, GETDATE()) AS ApproximateAge ,CONVERT(int, DATEDIFF(DAY, e.BirthDate, GETDATE())/365) AS Age FROM HumanResources.Employee as e inner join Person.Contact as c on e.ContactID = c.ContactID order by c.LastName |
比較這次的結果和上一個例子的結果,看看有什么不同。
可以看到,Nancy是31歲,其他雇員的年齡也精確到了天。表中的BirthDate列存儲雇員的生日,並以午夜(00:00:00AM)為界,這是一天中的第一秒。GETDATE()函數返回當前的時間與日期。當前兩個日期相差約8小時(寫這段文字時是上午8點)。如果希望這個計算更精確,就需要在當前日期的午夜把GETDATE()函數的結果轉換為datetime類型。
DATEPART()與DATENAME()函數
這兩個函數用於返回datetime或者shortdatetime值的日期部分。DATEPART()函數返回一個整型值;DATENAME()函數返回一個包含描述性文字的字符串。比如,將日期4-29-1988傳遞給DATEPART()函數,如指定返回月份值,則返回數字4:
SELECT DATEPART(MONTH, '4-29-1988')
而使用相同的參數,DATENAME()函數返回04(這取決於你的機器的本地語言,如果是英文版,那么將返回April):
SELECT DATENAME(MONTH, '4-29-1988')
這兩個函數都接收和DATEADD()函數一樣的時間間隔參數常量。
GETDATE()與GETUTCDATE()函數
這兩個函數都用於返回datetime類型的當前日期與時間。GETUTCDATE()函數使用服務器上的時區設置來求出UTC時間,這和格林威治標准時間或飛行員所說的"祖魯時"(Zulu Time)是一樣的。兩個函數都能精確到3.33毫秒。
SELECT GETDATE() SELECT GETUTCDATE() |
執行這兩個函數,都將返回未經格式化的結果,見下圖:
我在北京,和UTC時間相差8個小時,和標准時間相差9個小時。可以使用如下DATEDIFF()函數來驗證這個時間差值:
SELECT DATEDIFF(HOUR, GETDATE(), GETUTCDATE())
SYSDATETIME()和SYSUTCDATETIME()函數
這兩個SQL Server 2008函數等價於GETDATE()和GETUTCDATE()函數,但不是返回datetime數據類型的結果,而是返回SQL Server 2008新的datetime2數據類型的結果,該數據類型可以精確到100納秒,當然這取決於服務器安裝的硬件。
SELECT SYSDATETIME() SELECT SYSUTCDATETIME() |
DAY()、MONTH()和YEAR()函數
這三個函數分別返回以整數表示的datetime或者smalldatetime類型值的日、月、年。它們的用途很廣泛,如可以創建獨特的個性化日期格式。假設需要創建一個自定義的日期值作為字符串,通過將這三個函數的輸出結果轉換成字符類型,然后進行連接操作,就可以對輸出結果以任何形式進行組合了:
SELECT 'Year: ' + CONVERT(varchar(4), YEAR(GETDATE())) + ', Month: ' + CONVERT(varchar(2), MONTH(GETDATE())) + ', Day: ' + CONVERT(varchar(2), DAY(GETDATE())) |
這個腳本生成下列結果:
Year:2008, Month:2, Day:20
下一節將討論字符串操縱函數,並使用相似的技術來構建一個緊湊的定制時間戳。
字符串操縱函數
字符串函數可以解析、替換、操縱字符型值。在處理原始字符數據時,最大的挑戰之一是如何可靠地提取出有意義的信息。有很多字符串解析函數可用於標識和解析子字符串(一個大字符型值的一部分)。我們一直在做這種事,在我們閱讀文件、發票或者書面材料時,就會本能地標識、分離出有意義的信息片段。這個過程的自動化非常困難,即使是處理不太復雜的文本,也很困難。這些函數包含幾乎所有必需的工具,而挑戰在於如何找出最簡單、最高效的方法。
ASCII()、CHAR()、UNICODE()和NCHAR()函數
這四個函數是相似的,它們都可以在字符和字符的標准數字表示之間轉換。美國標准信息交換碼(American Standard Code for Information Interchange,ASCII)標准字符集包含128個字母、數字和標點符號。這個字符集是IBM PC體系結構的基礎,雖然有些字符現在看來已經很古老了,但還是被保留了下來,且仍是現代計算機技術的核心。如果在計算機上使用英語,則鍵盤上的每個字符都是用ASCII碼表示的。這對說英語(至少以英語打字)的計算機用戶來說是有利的,但是其他人又該怎么辦呢?
在計算機的發展過程中, ASCII字符集發布沒多長時間便過時了。人們很快將它擴展成為256個字符的ANSI字符集,一個字符用一個字節來保存。這個擴展的字符列表滿足了許多其他用戶的需求,可以支持主要的歐洲語言字符,不過仍是美國標准(由美國國家標准學會持有),仍建立在最初的英語字符集的基礎上。為了支持所有可印刷的語言,人們制訂了Unicode標准,它支持多種語言特定的字符集。每個Unicode字符需要2個字節的存儲空間,是ASCII與ANSI字符的兩倍。但是使用2個字就可以表示超過65 000個不同的字符,完全能夠支持東歐和亞洲字符。SQL Server同時支持ASCII與Unicode兩種標准。
ASCII()和CHAR()是兩個基於ASCII的函數,這兩個函數可將計算機上應用的每個字符表示為數字。要確定代表一個字符的數字是什么,就應給ASCII()函數傳送只包含一個字符的字符串,如下:
SELECT ASCII('A')
結果是65。
如要將一個已知數字轉換為字符,又該怎么辦?使用CHAR()函數即可:
SELECT CHAR(65)
結果是字母A。
要得到完整的ASCII字符值列表,可以對一個臨時表填充從0到127的數字,然后調用CHAR()函數返回相應的字符。為了節省空間,我們對以下這個腳本進行了刪節,但包含整個結果集,並以多欄格式給出。
-- 創建一個臨時表來保存ASCII碼: Create Table #ASCIIVals (ASCIIValue smallint) -- 插入數字0 - 127 到臨時表中: declare @Number int set @Number = 0 while(@Number < 128) begin Insert Into #ASCIIVals (ASCIIValue) Select @Number set @Number = @Number + 1 end -- 查詢所有的整型數字與其對應的ASCII碼: SELECT ASCIIValue, CHAR(ASCIIValue) AS Character FROM #ASCIIVals drop table #ASCIIVals |
表6-12是以多欄網格重新格式化的結果集。需要注意的是這里將不可印刷的控制字符以方括號表示。由於許多因素限制,如所安裝的字體或語言不同,下表的顯示可能會有稍許差異。
UNICODE()函數是ASCII()的Unicode等價函數,NCHAR()函數和CHAR()函數的功能相同,只不過NCHAR()是用於Unicode字符的。SQL Server的nchar與nvarchar類型能存儲任何Unicode字符,可以和這兩個函數一起使用。對於特別大的值,ntext類型和nvarchar(max)類型也支持Unicode字符。
要返回擴展字符編碼集中的字符,可以將字符編碼傳遞給NCHAR()函數:
SELECT NCHAR(220)
返回字母ü。
SELECT NCHAR(233)
返回帶重音符號的小寫e:é。
SELECT NCHAR(241)
當然,ASCII標准也支持所有的歐洲字符,所以使用CHAR()函數也可以返回這些擴展字符。如果對256~65536之間的值使用CHAR()函數,返回值就很有趣了。例如,下面的查詢返回希臘字符Ω:
SELECT NCHAR(433)
下面的查詢返回西里爾字母Ya(Я)。
SELECT NCHAR(1071)
CHARINDEX()和PATINDEX()函數
CHARINDEX()是原始的SQL函數,用於尋找在一個字符串中某子字符串第一次出現的位置。如函數名所示,這個函數返回一個整型值,表示某子字符串的第一個字符在整個字符串中的位置索引。以下腳本用於在字符串Washington中尋找子字符串sh的出現位置:
SELECT CHARINDEX('sh', 'Washington')
返回的結果是3,表明s是字符串Washington中的第3個字符。這說明CHARINDEX函數匹配字符的索引是從1開始的。如果沒有匹配到任何結果,函數將返回0。在這個例子中使用兩個字符作為子字符串並沒有特別意義,但是如果字符串包含多個s字符,就有意義了。
PATINDEX()函數和CHARINDEXO函數類似,它執行相同的操作,但方法稍許不同,該函數增加了對通配符(即Like運算符中使用的字符)的支持。顧名思義,它將返回一個字符模式的索引。這個函數也可以和ntext、nchar(max)和nvarchar(max)等大字符類型一起使用。注意,如果和這些大字符類型一起使用,PATINDEX()函數將返回bigint類型的值,而不是int類型的值。以下是一個例子:
SELECT PATINDEX('%M_rs%', 'The stars near Mars are far from ours')
注意,如果想找到一個字符串,在所比較的字符串的前后各有0個或者多個字符,則兩個百分符都是必須的。下划線表明這個位置上的字符不必匹配,它可以是任意字符。
和使用相同字符串的CHARINDEX()函數作一下比較:
SELECT CHARINDEX('Mars', 'The stars near Mars are far from ours')
這兩個函數都返回索引值16。請注意這些函數的執行過程。下一節將把這兩個函數和SUBSTRING()函數組合在一起,演示如何使用界定符解析字符串。
LEN()函數
LEN()函數用於返回一個代表字符串長度的整型值。這是一個簡單、有用的函數,經常與其他函數一起使用,來應用業務規則。以下例子將月份和日期轉換為字符類型,然后測試它們的長度。如果月份日期只有一個字符,就填充字符0,然后組合成一個8字符的美國格式的日期字符串(MMDDYYYY)。
DECLARE @MonthChar varchar(2), @DayChar varchar(2), @DateOut char(8) SET @MonthChar = CAST(MONTH(GETDATE()) AS varchar(2)) SET @DayChar = CAST(DAY(GETDATE()) AS varchar(2)) -- Make sure month and day are two char long: IF LEN(@MonthChar) = 1 SET @MonthChar = '0' + @MonthChar IF LEN(@DayChar) = 1 SET @DayChar = '0' + @DayChar -- Build date string: SET @DateOut = @MonthChar + @DayChar + CAST(YEAR(GETDATE()) ASchar(4)) SELECT @DateOut AS OutputDate |
這個腳本將返回代表日期的8個字符:
08152010
LEFT()和RIGHT()函數
LEFT()與RIGHT()函數是相似的,它們都返回一定長度的子字符串。這兩個函數的區別是,它們返回的分別是字符串的不同部分。LEFT()函數返回字符串最左邊的字符,順序從左數到右。RIGHT()函數正好相反,它從最右邊的字符開始,以從右到左的順序返回特定數量的字符。看一看使用這兩個函數返回"GeorgeWashington"這個字符串的子字符串的例子。
如果使用LEFT()函數返回一個5字符的子字符串,則函數先定位最左邊的字符,向右數5個字符,然后返回這個子字符串,如下所示。
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' SELECT LEFT(@FullName, 5) |
結果為:Georg
如果使用RIGHT()函數返回一個5字符的子字符串,則函數先定位最右邊的字符,向左數5個字符,然后返回這個子字符串,如下所示。
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' SELECT RIGHT (@FullName, 5) |
結果為:ngton
要想返回字符串中有意義的部分,這兩個函數都不是特別有用。如果想返回全名中的姓氏或者名字,該怎么辦?這需要多做一點工作。如果能確定每個姓名中空格的位置,就可以使用LEFT()函數在全名中讀取名字。在這種情況下,可以使用CHARINDEX()或者PATINDEX()函數來定位空格,然后使用LEFT()函數返回空格前的字符。下面是第一個用過程方法編寫的例子,它將處理過程分解成以下步驟:
DECLARE @FullName varchar(25), @SpaceIndex tinyint SET @FullName = 'George Washington' -- Get index of the delimiting space: SET @SpaceIndex = CHARINDEX(' ' , @FullName) -- Return all characters to the left of the space: SELECT LEFT(@FullName, @SpaceIndex - 1) |
結果為:George
如果不想在結果中包含空格,就需要從@SpaceIndex值中減去1,這樣結果中就只有名字了。
SUBSTRING()函數
SUBSTRING()函數能夠從字符串的一個位置開始,往右數若干字符,返回一個特定長度的子字符串。和LEFT()函數不同之處是,該函數可以指定從哪個位置開始計數,這樣就可以在字符串的任何位置摘取子字符串了。這個函數需要三個參數:要解析的字符串、起始位置索引、要返回的子字符串長度。如果要返回到所輸入字符串尾部的所有字符,可以使用比所需長度更大的長度值。SUBSTRING()函數將返回最大可能長度的字符數,而不會將多出的長度以空格填充。
只要指定字符串最左邊的字符(1)為起始索引,就可以用SUBSTRING()函數替代LEFT()函數。
繼續上一節的例子。可以設置起始位置與長度,返回姓名字符串中間的值。在這個例子中,從位置4開始,返回一個6字符的子字符串"rge Wa"。
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' SELECT SUBSTRING(@FullName, 4, 6) |
現在將上述各函數組合在一起,即可從名字+空格+姓氏格式的全名字符串中解析出名字和姓氏。使用先前的邏輯,通過函數嵌套來減少腳本的行數,並去掉@SpaceIndex變量。下面用SUBSTRING()函數替代LEFT()函數:
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' -- Return first name: SELECT SUBSTRING(@FullName, 1, CHARINDEX(' ', @FullName) - 1) |
類似的邏輯可以用於解析姓氏,但是必須將起始位置更改為空格后的那個字符。如果空格在第7個位置上,那么姓氏將從第8個位置開始。這就意味着起始位置是CHARINDEX()的返回結果加上1。
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' --Return last name: SELECT SUBSTRING(@FullName, CHARINDEX(' ', @FullName) + 1, LEN(@FullName)) |
把上述步驟組合在一起,就可以運行下面的查詢,從全名變量中提取出名字和姓氏:
DECLARE @FullName varchar(25) SET @FullName = 'George Washington' -- Return first name: SELECT SUBSTRING(@FullName, 1, CHARINDEX(' ',@FullName) - 1) ASFirstName, SUBSTRING(@FullName, CHARINDEX(' ',@FullName) + 1,LEN(@FullName)) AS LastName |
傳遞給SUBSTRING()函數的值是空格所在位置加上1,並將該值作為起始位置,這將是姓氏的第1個字母。由於不可能總是知道名字的長度,所以將LEN()函數的結果作為子字符串長度參數傳遞進來,當SUBSTRING()函數到達這個位置時,就到達了字符串的末尾,這樣就可以將字符串中從空格后面開始的所有字符都包含進來了。
為了舉例方便,先創建並填充一個臨時表:
CREATE TABLE #MyNames (FullName varchar(50)) GO INSERT INTO #MyNames (FullName) SELECT 'Fred Flintstone' INSERT INTO #MyNames (FullName) SELECT 'Wilma Flintstone' INSERT INTO #MyNames (FullName) SELECT 'Barney Rubble' INSERT INTO #MyNames (FullName) SELECT 'Betty Rubble' INSERT INTO #MyNames (FullName) SELECT 'George Jetson' INSERT INTO #MyNames (FullName) SELECT 'Jane Jetson' go --drop table #MyNames |
下面執行一個使用函數調用來解析名字和姓氏值的單行查詢表達式。這里對@FullName變量的引用被表中的FullName列所替代:
SELECT SUBSTRING(FullName, 1, CHARINDEX(' ', FullName) - 1) AS FirstName ,SUBSTRING(FullName, CHARINDEX(' ', FullName) + 1, LEN(FullName))AS LastName FROM #MyNames |
在下圖所示的結果中,顯示了兩個不同的列,分別是名字和姓氏。
LOWER()和UPPER()函數
這兩個函數很容易理解,它們用於將字符串中所有字符分別都轉換為小寫和大寫,這在比較用戶輸入或者存儲用於比較的字符串時是非常有用的。字符串比較通常是區分大小寫的,這取決於SQL Server安裝時的設置。如果和其他的字符串操縱函數一起使用,就可以將字符串轉換為合適的大小寫,以便存儲或顯示。以下例子說明混合大小寫的名字,假設名字中的第2個大寫子字符串前只包含一個空格,但在特殊情況下也有一些名字是沒有空格的。這個例子很容易通過擴展來處理包含其他類型的混合大小寫名字(如以MC開頭的名字,帶連接號的名字等)。
DECLARE @LastName varchar(25), @SpaceIndex tinyint SET @LastName = 'mc donald' -- Test value -- Find space in name: SET @SpaceIndex = CHARINDEX(' ' , @LastName) IF @SpaceIndex > 0 -- Space: Capitalize first & substring SELECT UPPER(LEFT(@LastName, 1)) + LOWER(SUBSTRING(@LastName, 2, @SpaceIndex - 1)) + UPPER(SUBSTRING(@LastName, @SpaceIndex + 1, 1)) + LOWER(SUBSTRING(@LastName, @SpaceIndex + 2, LEN(@LastName))) ELSE -- No space: Cap only first char. SELECT UPPER(LEFT(@LastName, 1)) + LOWER(SUBSTRING(@LastName, 2, LEN(@LastName))) |
這個腳本將返回MC Donald。還可以對這個例子進行擴展,以處理姓氏包含撇號的情況。在這個例子的業務規則中,空格是不考慮的。如果找到了撇號,就將后面的字符全部轉為大寫。請注意如果要在腳本中測試撇號,就必須輸入兩次撇號(' '),以表明這是一個文字,而不是一對單引號。姓氏中只存儲一個撇號。
DECLARE @LastName varchar(25), @SpaceIndex tinyint, @AposIndex tinyint SET @LastName = 'o''malley' -- Test value -- Find space in name: SET @SpaceIndex = CHARINDEX(' ', @LastName) -- Find literal ' in name: SET @AposIndex = CHARINDEX('''', @LastName) IF @SpaceIndex > 0 -- Space: Capitalize first & substring SELECT UPPER(LEFT(@LastName, 1)) + LOWER(SUBSTRING(@LastName, 2, @SpaceIndex - 1)) + UPPER(SUBSTRING(@LastName, @SpaceIndex + 1, 1)) + LOWER(SUBSTRING(@LastName, @SpaceIndex + 2, LEN(@LastName))) ELSE IF @AposIndex > 0 -- Apostrophe: Cap first & substring SELECT UPPER(LEFT(@LastName, 1)) + LOWER(SUBSTRING(@LastName, 2, @AposIndex - 1)) + UPPER(SUBSTRING(@LastName, @AposIndex + 1, 1)) + LOWER(SUBSTRING(@LastName, @AposIndex + 2, LEN(@LastName))) ELSE -- Nospace: Cap only first char. SELECT UPPER(LEFT(@LastName, 1)) + LOWER(SUBSTRING(@LastName, 2, LEN(@LastName))) |
這個腳本返回O'Malley。
LTRIM()和RTRIM()函數
這兩個函數分別返回將字符串的左邊和右邊的空白修剪掉之后的字符串:
DECLARE @Value1 char(10), @Value2 char(10) SET @Value1 = 'One' SET @Value2 = 'Two' SELECT @Value1 + @Value2 SELECT CONVERT(varchar(5), LEN(@Value1 + @Value2)) + ' characters long. ' SELECT RTRIM(@Value1) + RTRIM(@Value2) SELECT CONVERT(varchar(5), LEN(RTRIM(@Value1) + RTRIM(@Value2))) + ' characters long trimmed. ' |
結果如下:
REPLACE()函數
REPLACE()函數可以把字符串中的某個字符或某個子字符串替換為另一個字符或者子字符串,該函數可以用於全局查找和替換工具中。
DECLARE @Phrase varchar(1000) SET @Phrase = 'I aint gunna use poorgrammar when commenting script and I aint gunna complain about it. ' SELECT REPLACE(@Phrase, 'aint', 'am not') |
REPLICATE()和SPACE()函數
在需要將一些字符重復填充進一個字符串時,這兩個函數是非常有用的。這里也使用SUBSTRING()例子中的臨時表為每個名字填滿20個字符,然后將20減去各個字符串的長度,以便將正確的值傳遞給REPLICATE()函數:
SELECT FullName + REPLICATE('*', 20 - LEN(FullName)) FROM #MyNames |
結果是每個名字后面都填滿了星號,各個名字的總長度都是20個字符:
Fred Flintstorle*****
Wilrna Flintstone****
Barney Rubble*******
Betty Rubble********
George Jetson********
Jane Jetson**********
SPACE()函數與上述函數類似,區別在於該函數使用空格進行填充。它返回一個由空格組成的字符串,空格的個數由參數定義。
SELECT FullName + SPACE(20 - LEN(FullName)) FROM #MyNames |
如果返回"#MyNames表不存在"的錯誤,只需再次運行本文前面"SUBSTRING()函數"一節的CREATE TABLE腳本即可。
REVERSE()函數
顧名思義,這個函數用於將字符串中的字符顛倒過來。這在處理連接列表中的單個字符值時將會被用到。
SELECT REVERSE('The stars near Mars are far from ours. ')
結果為:.sruo morf raf era sraM raen srats ehT
STUFF()函數
這個函數可將字符串中的一部分替換為另一個字符串。它本質上是將一個字符串以特定的長度插入另一個字符串中的特定位置上。這對於源值與目的值的長度不一樣的字符串替換是很有用的。下列代碼用於將字符串中的價格替換為109.95:
Please submit your payment for 99.95 immediately.
價格值是從第32個字符開始的,有5個字符長。在這個位置上插入的子字符串有多長並不重要,只需要知道需要刪除多少個字符就可以了。
SELECT STUFF('Please submit your payment for 99.95 immediately. ', 32, 5, '109.95')
結果為:Please submit your payment for 109.95 immediately.
QUOTENAME()函數
這個函數和SQL Server對象名組合使用,以將結果傳遞給表達式。它只用於給輸入的字符串加一對方括號,並返回新形成的字符串。如果參數包含保留的分隔符或者封裝字符(比如引號或括號),這個函數將修改字符串,以便SQL Server能將結果字符串中的這類字符當成文本字符。如下面的例子所示,查詢的結果如圖6-10所示。
SELECT QUOTENAME(COLUMN_NAME) AS ColumnName FROM INFORMATION_SCHEMA. COLUMNS |
數學函數
下表中列出的函數用於執行多種普通與特殊的數學運算,可以執行代數、三角、統計、估算與財政運算等運算。
函 數 |
說 明 |
ABS() |
返回一個數的絕對值 |
ACOS() |
計算一個角的反余弦值,以弧度表示 |
ASIN() |
計算一個角的反正弦值,以弧度表示 |
ATAN() |
計算一個角的反正切值,以弧度表示 |
ATN2() |
計算兩個值的反正切,以弧度表示 |
CEILING() |
返回大於或等於一個數的最小整數 |
COS() |
計算一個角的正弦值,以弧度表示 |
COT() |
計算一個角的余切值,以弧度表示 |
DEGREES() |
將一個角從弧度轉換為角度 |
EXP() |
指數運算 |
FLOOR() |
返回小於或等於一個數的最大整數 |
LOG() |
計算以2為底的自然對數 |
LOG10() |
計算以10為底的自然對數 |
PI() |
返回以浮點數表示的圓周率 |
POWER() |
冪運算 |
RADIANS() |
將一個角從角度轉換為弧度 |
RAND() |
返回以隨機數算法算出的一個小數, 可以接收一個可選的種子值 |
ROUND() |
對一個小數進行四舍五入運算, 使其具備特定的精度 |
SIGN() |
根據參數是正還是負,返回–1或者1 |
SIN() |
計算一個角的正弦值,以弧度表示 |
SQRT() |
返回一個數的平方根 |
SQUARE() |
返回一個數的平方 |
TAN() |
計算一個角正切的值,以弧度表示 |
元數據函數
這是一些工具函數,它們返回SQL Server配置細節、服務器與數據庫設置細節的信息,包括一組用於返回不同對象的屬性狀態的通用以及專用函數,這些函數把對Master數據庫中系統表以及用戶數據庫的查詢封裝在函數中。建議讀者使用這些函數以及其他的系統函數,而不是自己創建對系統表的查詢,以防今后SQL Server版本對模式進行更改。
排列函數
這些函數被用於以與結果集順序無關的特定順序,枚舉已排序的或排在前面的結果集。
ROW_NUMBER()函數
ROW_NUMBER()函數根據作為參數傳遞給這個函數的ORDER BY子句的值,返回一個不斷遞增的整數值。如果ROW_NUMBER的ORDER BY的值和結果集中的順序相匹配,返回值將是遞增的,以升序排列。如果ROW_NUMBER的ORDER BY子句的值和結果集中的順序不同,這些值將不會按順序列出,但它們表示ROW_NUMBER函數的ORDER BY子句的順序。如下面的例子和結果所示:
SELECT ProductCategoryID ,Name ,ROW_NUMBER() OVER (ORDER BY Name) AS RowNum FROM Production.ProductCategory ORDER BY Name |
由於ROW_NUMBER()調用中的ORDERBY子句和查詢結果的順序匹配,所以對這些結果按順序列出,如下圖所示:
不過,在函數調用中使用另一個ORDER BY子句時,這些值就是無序的了。
SELECT ProductCategoryID ,Name ,ROW_NUMBER() OVER (ORDER BY Name) AS RowNum FROM Production.ProductCategory ORDER BY ProductCategoryID |
這是了解如何使用ORDER BY子句對結果進行排序的有效方法。如下圖所示:
RANK()與DENSE_RANK()函數
這兩個函數與ROW_NUMBER()函數類似,因為它們都返回一個基於ORDER BY子句的值。不過這些值不一定永遠是唯一的。排列值對於所提供的ORDER BY子句中的重復結果而言也是重復的,而且唯一性是僅僅基於ORDER BY列表中的唯一值的。這些函數用不同的方法來處理重復的值。RANK()函數保留列表中行的位置序號,對於每個重復的值,該函數會跳過下面與其相鄰的值,於是就可以將下一個不重復的值保留在正確的位置上。
其行為類似於短跑比賽中的並列成績。例如劉翔與Dayron Robles(古巴)在110欄的比賽中都跑出了12’92的成績,那他們就是並列第一,而其后的一名選手將會獲得第三名的成績。
SELECT ProductID ,Name ,ListPrice ,RANK() OVER (ORDER BY ListPrice DESC) AS [Rank] FROM Production.Product ORDER BY [Rank] |
注意在下圖的結果列表中,重復的價格值所對應的結果是相同的,而每個連接之后的值都被跳過了。比如,產品"Road-150 Red, 52"和"Road-150 Red, 56"都排在第1,而接下來的行"Mountain-100 Silver,38"就排在第6了。
DENSE_RANK()函數的工作方式與RANK()函數相同,不過它不會跳過每個連接后的值,這樣就不會有值被跳過了,但是在連接處排列序號位置將會丟失。
SELECT ProductID ,Name ,ListPrice ,DENSE_RANK() OVER (ORDER BY ListPrice DESC) AS [Rank] FROM Production.Product ORDER BY [Rank] |
下圖的結果重復了排列值,但是不會跳過列中的任何數字。
NTILE(n)函數
這個函數也用於對結果進行排列,並返回一個整型的排列值,但是它不會對結果以唯一的排列順序進行枚舉,而是將結果切分為有限數量的排列組。比如,一個表有10 000行,使用1000為參數值調用NTILE()函數,即NTILE(1000),並將結果分成以10為單位的1000個組,每個組賦予相同的排列值。和本節討論的其他排列函數一樣,NTILE()函數也支持OVER(ORDER BY…)語法。下面的例子根據產品價格,按照從高到低的順序把Product表分為50組產品:
SELECT ProductID ,Name ,ListPrice ,NTILE(50) OVER (ORDER BY ListPrice DESC) AS GroupedProducts FROM Production.Product ORDER BY GroupedProducts |
結果為:
安全函數
與安全相關的函數返回SQL Server用戶的角色成員和權限信息。這類函數也包括一組管理事件與跟蹤的函數。下表顯示了這些函數:
函 數 |
說 明 |
fn_trace_geteventinfo() |
為指定的跟蹤ID返回一個填充事件信息的表類型值 |
fn_trace_getfilterinfo() |
為指定的跟蹤ID返回一個填充與過濾器有關的信息的表類型值 |
fn_trace_getinfo() |
為指定的跟蹤ID返回一個填充跟蹤信息的表類型值 |
fn_trace_getable() |
為指定的跟蹤ID返回一個填充文件信息的表類型值 |
HAS_DBACCESS() |
返回一個表明當前用戶是否有訪問指定數據庫權限的標志 |
IS_MEMBER() |
返回一個表明當前用戶是Windows組用戶還是SQL Server用戶的標志 |
IS_SRVROLEMEMBER() |
返回一個表明當前用戶是否是數據庫服務器角色成員的標志 |
SUSER_SID() |
返回指定用戶的登錄名的安全ID,或者(如果參數被忽略)返回當前用戶的安全ID。返回指定用戶的用戶ID,或者(如果參數被忽略的話)返回當前用戶的用戶ID |
SUSER_SNAME() |
返回指定安全ID的登錄名。如果不提供任何安全ID,則返回當前用戶的登錄名 |
USER_ID() |
返回指定用戶名的用戶ID,或者(如果參數被忽略的話)返回當前用戶的用戶ID |
USER_NAME() |
返回指定用戶ID的用戶名 |
系統函數與系統變量
本節討論具有多種用途的工具函數,包括值比較、值類型測試等功能。這個類別的函數也包羅了其他函數:
函 數 |
說 明 |
APP_NAME() |
返回與當前連接相關聯的應用程序的名字 |
COALESCE() |
從以逗號分隔的表達式列表中返回第一個非空值 |
COLLATIONPROPERTY() |
返回一個特定字符集排序規則的特定屬性的值。這些屬性包括CodePage、LCID、ComparisonStyle |
CURRENT_TIMESTAMP() |
返回當前日期與時間。和GETDATE()函數是同義的。這個函數的存在只是為了與ANSI-SQL兼容 |
C1UJRRENT_USER() |
返回當前用戶的名字。與USER_NAME()函數相同 |
DATALENGTH() |
返回存儲或處理一個值所需的字節數。對於ANSI字符串類型,這個函數返回的值與LEN()函數相同,但對於其他數據類型而言就可能不一定相同了 |
fn_helpcollations() |
返回一個填充有由當前SQLSewer版本支持的字符集排序規則的表類型值 |
fn_servershareddrives() |
返回一個填充有服務器共享的驅動列表的表類型值 |
fn_virtualfilestats() |
返回一個填充有包括日志文件在內數據庫文件的I/O狀態的表類型值 |
FORMATMESSAGE() |
從sysmessages表中為指定的信息代碼和以逗號分隔的參數列表返回錯誤信息 |
GETANSINULL() |
根據ANSLNULL_DFLT_ON與ANSLNULL_DFLT_OFF數據庫設置返回數據庫的可空性設置 |
HOST_ID() |
返回當前會話的工作站ID |
HOST_NAME() |
返回當前會話的工作站名 |
IDENT_CURRENT() |
返回最后一個為指定的表生成的標識(ID)值。與會話、范圍無關 |
IDENT_INCR() |
返回最后一次創建的標識(ID)列中定義的增量值 |
IDENT_SEED() |
返回最后一次創建的標識(ID)列中定義的種子值 |
IDENTITY() |
用在SELECT…INTO語句中,在一個列中插入自動生成的標識值 |
ISDATE() |
返回一個表明指定的值是否可被轉換為日期值的標志 |
ISNULL() |
判斷指定的值是否是空值,然后返回一個事先提供的替代值 |
ISNUMERIC() |
返回一個表明指定的值是否可被轉換為數字值的標志 |
NEWID() |
返回一個新生成的UniqueIdentifier類型的值。這是一個128位的整型、全球唯一的值,通常以字母或數字十六進制來表示(如89DE6247·C2E242DB-8CE8·A787E505D7EA)。這個類型經常被用作復制的和半連接系統中的主鍵. |
NULLIF() |
兩個特定的參數的值如果是相同的,則返回NULL |
PARSENAME() |
返回一個具有4部分對象名的特定部分 |
PERMISSIONS() |
返回一個整型值,該值是一個表示當前用戶在指定的數據庫對象上權限或者權限組合的位映像 |
ROWCOUNT_BIG() |
與@@RowCount變量一樣,這個函數返回被最后一條語句修改或返回的行數量。返回值類型是bigint |
SCOPE_IDENTITY() |
與@@IDENTIY變量一樣,這個函數返回限制在當前會話與范圍內的最后一次生成的標識值 |
SERVERPROPERTY() |
返回一個表示服務器屬性狀態的標記。屬性包括Collation、Edition、EngineEdition、InstanceName、IsClustered、IsFullTextInstalled、IsIntegrated- SecurityOnly、IsSingleUser、IsSyncWithBackup、LicenseTYpe、MachineName、NumLicenses、ProcessID、ProductLevel、ProductVersion、ServerName |
SESSION_USER |
返回當前用戶名。調用本函數不需要括號 |
SESSIONPROPERTY() |
返回表示一個會話屬性狀態的標記。屬性包括:ANSL_NULLS,ANSI_PADDING,ANSL_WARNINGS,ARITHABORT,CONCAT_NULL_ YIELDS_NULL,NUMERIC_ROUNDABORT,QUOTED_IDENTIFIER |
STATS_DATE() |
返回指定的索引統計信息最后一次被更新的時間 |
SYSTEM_USER |
返回當前用戶名。調用本函數不需要括號 |
USER_NAME() |
為一個指定的用戶ID返回用戶名。如果沒有提供ID號則返回當前的數據庫用戶 |
COALESCE()函數
COALESCE()函數是非常有用的,它返回其參數中第一個非空表達式。它能夠節省頗多IF或者CASE分支邏輯。以下例子用產品數據填充一個表,每個產品最多有3種價格:
CREATE TABLE #ProductPrices (ProductName varchar(25), SuperSalePriceMoney NULL, SalePrice Money NULL, ListPrice Money NULL) GO INSERT INTO #ProductPrices VALUES('Standard Widget', NULL, NULL,15.95) INSERT INTO #ProductPrices VALUES('Economy Widget', NULL, 9.95, 12.95) INSERT INTO #ProductPrices VALUES('Deluxe Widget', 19.95, 20.95,22.95) INSERT INTO #ProductPrices VALUES('Super Deluxe Widget', 29.45, 32.45,38.95) INSERT INTO #ProductPrices VALUES('Executive Widget', NULL, 45.95,54.95) GO |
所有的產品都有定價,有些有銷售價,有些還有促銷價。一項產品的當前價格是所有己有價格的最低價,或者在讀取每個價格列時以列出順序讀到的第一個非空值:
SELECT ProductName, COALESCE(SuperSalePrice, SalePrice, ListPrice) ASCurrentPrice FROM #ProductPrices |
這個方法比使用多行分支與判斷邏輯要簡潔得多,而結果也是同樣簡單,如下圖所示:
DATALENGTH()函數
DATALENGTH()函數返回一個用於對值進行管理的字節數,這有助於揭示不同數據類型間的一些有趣差別。當把varchar類型傳遞給DATALENGTH()和LEN()函數時,它們將返回相同的值:
DECLARE @Value varchar(20) SET @Value = 'abc' SELECT DATALENGTH(@Value) SELECT LEN(@Value) |
這些語句的返回值都為3。因為varchar類型使用了3個單字節字符來存儲三個字符的值。然而,如果使用nVarchar類型來管理相同長度的值,就要占用多一倍的字節:
DECLARE @Value nvarchar(20) SET @Value = 'abc' SELECT DATALENGTH(@Value) SELECT LEN(@Value) |
DATALENGTH()函數返回值為6,因為每個使用Unicode字符集的字符都要占用2個字節。LEN()函數返回值為3,因為這個函數返回字符數,不是字節數。以下是一個有趣的測試:要存儲一個值為2的整型變量,要占用多少個字節?而如果要存儲一個值為20億的整型變量,又將占用多少個字節呢?試一下:
DECLARE @Value1 int, @Value2 int SET @Value1 = 2 SET @Value2 = 2000000000 SELECT DATALENGTH(@Value1) SELECT LEN(@Value1) SELECT DATALENGTH(@Value2) SELECT LEN(@Value2) |
在這兩種情況下,DATALENGTH()函數都返回4。因為int類型不論值是多少,總是使用4個字節。LEN()函數本質上將整型值當成已轉換成字符型的數據來處理,所以,在這個例子中,它分別返回1和10,即值的位數。
在下表中的全局系統變量都將返回int類型的值。這些變量可用於存儲過程和其他實現定制業務邏輯的編程對象。
變 量 |
說 明 |
@@ERROR |
當前會話最后一次發生的錯誤代碼 |
@@IDENTITY |
當前會話最后一次生成的標識值 |
@@ROWCOUNT |
當前會話中最后一次返回結果集的執行操作所返回的行數 |
@@TRANCOUNT |
當前會話中活動的事務數。這是在執行相關的COMMIT TRANSACTION或者ABORT TRANSACTION語句之前嵌套的多個BEGIN TRANSACTION語句的結果 |
系統統計變量
下表描述了用於確定數據庫系統使用信息與環境信息的管理工具:
變 量 |
說 明 |
@@CONNECTIONS |
返回打開連接的次數 |
@@CPU_BUSY |
從上次啟動服務器開始,SQL Server一共工作的毫秒數 |
@@IDLE |
從上次啟動服務器開始,SQL Server一共空閑的毫秒數 |
@@IO_BUSY |
從上次啟動服務器開始,SQL Server一共處理I/0的毫秒數 |
@@PACK_RECEIVED |
從上次啟動服務器開始,SQL Server一共收到的網絡數據包數 |
@@PACK_SENT |
從上次啟動服務器開始,SQL Server一共發送的網絡數據包數 |
@@PACKET_ERRORS |
從上次啟動服務器開始,SQL Server一共收到的網絡數據包錯誤數 |
@@TIMETICKS |
每個時鍾滴答有多少毫秒 |
@@TOTAL_ERRORS |
從上次啟動服務器開始,SQL Server一共收到的磁盤I/O錯誤數 |
@@TOTAL_READ |
從上次啟動服務器開始,SQL Server一共進行的物理磁盤讀取次數 |
@@TOTAL_WRITE |
從上次啟動服務器開始,SQL Server一共進行的物理磁盤寫入次數 |
小結
函數用於實現業務邏輯,並且能夠將編程功能帶入查詢中。許多有用而且強大的函數是T-SQL的標准功能。和面向過程、面向對象語言中的函數一樣,SQL函數也將程序功能封裝到一個簡單的可重用的包中,這就減少了查詢設計人員的很多工作。由於Transact-SQL是面向任務的語言,而不是過程語言。雖然函數可以進行過程編程,可以在查詢中構建頗為復雜的邏輯,但是SQL語言的優勢在於讓設計人員表達出設計意圖,而不是完成一項任務的確切步驟與方法。只要使用方法正確,這些步驟和方法都可以由函數來實現。
在T-SQL中,參數用於將值傳遞給函數,大多數函數的返回結果是一個標量,或者說單一值。函數分為確定性函數與非確定性函數。在使用相同的參數時,確定性函數總是返回相同的值,而非確定性函數的返回值則與其他資源有關,所以SQL Server必須顯式地執行這種函數。因此,在定制的SQL編程對象中,對非確定性函數的使用是有限制的。
SQL函數執行種類繁多的重要任務,包括數學運算、比較、日期解析與操縱、高級字符串操縱等。
轉載自:http://www.cnblogs.com/moss_tan_jun/archive/2010/08/23/1806861.html