每種資料庫均有其特性及限制,SQL Server在數值運算上,有一些特性與一般概念上有些許之差異,因此在Schema設計或撰寫SQL時,需特別注意否則將出現「溢位(Overflow)」、「捨位」誤差等。以下針對可能發生的問題進行說明。
捨位問題
在MSDN官方文件在除法運算中提及「如果整數 dividend 除以整數 divisor,結果就是整數[1],結果的任何小數點後數字部分都會截斷。」,先前探討餘數、商數時也應用此項特性,直接將7除以3所得的結果即為商數2;而若需得到其真實運算結果2.33333(7/3)則該如何達成,在此將重提SQL SERVER數學運算上重要的概念,即官方網站中資料類型優先順序所提及「當一個運算子結合兩個不同資料類型的運算式時,資料類型優先順序的規則,會指定將低優先順序的資料類型,轉換為高優先順序的資料類型。[2]」,資料類型優先順序請參考前述之官方網站說明。以下將自7除以3為範例,將列舉出下述四種不同作法及其結果。
測試
|
運算
|
結果
|
資料類型
|
1
|
7/ 3
|
2
|
int(整數)
|
2
|
7.0 /3 或 7 / 3.0
|
2.333333
|
numeric
|
3
|
CAST(7 AS numeric) / 3
|
2.333333
|
numeric
|
4
|
CAST(1 AS float) * 7 / 3
|
2.33333333333333
|
float
|
l 測試1
結果為商數(Quotient),7及3均為整數(int),結果也將為整數。
l 測試2
將運算元(Operand)即7及3直接補上.0,即代表將int轉換成numeric。
l 測試3
利用CAST型態轉換函數,將運算元型態進行轉換。
l 測試4
額外乘上浮點數1的方法,此方法主要想法是不去更動原運算元,由於此方法額外乘上浮點數1,因此運算式中運算元將增加為3,在此探討額外應注意的事項為「順序」。以下調整測試4中的運算元順序如下,並列出運算結果。
運算
|
結果
|
CAST(1 AS float) * 7 / 3
|
2.33333333333333
|
7 / 3 * CAST(1 AS float)
|
2
|
範例中所使用的乘法(*)或除法(/)運算子(Operator)兩者具有相同的運算優先順序,當運算式中的兩個運算子有相同的運算子優先順序等級時,會依據它們在運算式中的位置,由左至右來運算及評估。7 / 3 * CAST(1 AS float)運算中7/3將優先被處理,運算結果為2(整數),即取得商數的作法,再將此結果與後續運算子進行運算,即2 * CAST(1 AS float)運算結果仍為2。
前述範例中,探討一般概念對於除法運算結果可為浮點數的刻版印象而造成的「捨位」誤差,再次強調SQL SERVER中整數除法運算後結果仍為整數的規則。在此將探討另外一項重要問題「溢位(Overflow)」。
溢位(Overflow)問題
以下探討乘法運算時,運算結果超過原資料型態的上限而發生溢位錯誤,將以1百萬(Million、10^6)為範例資料進行106 * 106 / 106 = 106運算測試,如下列兩組SQL語法所示:
測試資料1000000,就其數值範圍其型態應歸為整數(int),int的數值範圍為-2^31 (-2,147,483,648)到2^31-1(2,147,483,647)。
#
|
SQL
|
結果
|
1
|
SELECT Million*Million / Million
FROM
(
SELECT 1000000 Million
) A
|
Arithmetic overflow error converting expression to data type int.
|
2
|
SELECT CAST(Million AS bigint)* Million / Million
FROM
(
SELECT 1000000 Million
) A
|
1000000
|
l 測試1
SQL中Million*Million結果將為10^12(值相當於2^39.86,可用LOG對數函數計算即得到),因資料值已超過int資料型態上限,將造成溢位錯誤。
l 測試2
SQL是將資料轉換成bigint,用以承受此運算值範圍,此範例僅在於強調運算時將可能發生溢位錯誤,實務上常發生於SUM()加總函數上,當針對資料型態為int的欄位進行彙總運算,由於SUM() 函數傳入引數的欄位為int,回傳結果型態也為int,將以兩筆2^30(1073741824)資料加總為例,因加總值超過int允許範圍而發生溢位錯誤。
SQL
|
結果
|
SELECT SUM(Val)
FROM
(
SELECT POWER(2, 30) Val --1073741824
UNION ALL
SELECT POWER(2, 30)
) A
|
Arithmetic overflow error converting expression to data type int.
|
以下將SQL SERVER 2005及2008於官方文件上所定義的SUM函數的「傳回類型」合併表列如下。
SQL SERVER 2005
|
SQL SERVER 2008
|
|||||||||||||||||||||||||||
|
|
對於SQL SERVER 2005(含以前)版本若運算結果為整數類別則均傳回int,當運算結果為bigint,而傳回類型卻仍定義為int,因而造成溢位錯誤,就SQL SERVER 2008文件上說明此版本已修正此限制,但實際測試時卻仍發生相同問題。
函數引數(Argument)問題
以RADIANS函數將角度(deg)30度轉換為弧度(rad),傳入引數(Argument)將影響傳回結果,官方文件定義傳回資料型態類型與引數相同,此與一般概念所差異,使用上請多留意。測試如下所示。
測試
|
RADIANS語法
|
|||||||
|
RADIANS ( numeric_expression )
傳回類型
傳回與 numeric_expression 相同的類型
|
l 當傳入30.0(浮點數)時函數傳回值為0.5236(浮點數)
l 當傳入30(整數)時傳回則為0(整數)
綜合前述SQL SERVER中數學運算時可能發生溢位或捨位的問題,整理出下列說明。
1. 運算元兩者中擇高優先順序
當運算子為乘法時,因運算元均為整數而可能發生溢位的問題。當為除法時,會誤將商數當成一般運算結果,而發生捨位誤差的問題。當運算子均為相同優先順序時,運算會自左而右依序運算的特性,值得特別注意。
A
|
*
|
B
|
=
|
R
|
運算子
|
資料型態由A及B兩者中擇高優先順序的資料型態。
|
2. 引數
對於許多函數如RADIANS、DEGREES等,傳回資料型態是由引數所決定,因此,呼叫時需特別將引數轉換成輸出結果預期的資料型態。
RADIANS
|
(
|
A
|
)
|
=
|
R
|
函數
|
引數
|
資料型態由引數所決定
|
3. 運算式結果
在SQL SERVER 2005(含)以前版本,許多函數運算結果若為整數類別時,回傳僅以int代表,當運算結果已達bigint範圍將造成溢位錯誤。雖2008版本官方文件說明已修正此限制,但實測上仍相同。
SUM
|
(
|
A
|
)
|
=
|
R
|
函數
|
引數
|
資料型態由運算式結果所決定,但回傳整數類別時僅以int代表
|
謝謝您的解釋,非常的清楚,我有引用您的文章在我的個人部落格(https://iamswc.space),若有不妥,請告知,謝謝
回覆刪除