2013年8月26日 星期一

MSSQL數學運算應注意事項

每種資料庫均有其特性及限制,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
:請特別注意numericfloat之差異,當轉換為float時,可能因成Analog Digital之轉換而發生捨位誤差。

l   測試1
結果為商數Quotient),73均為整數(int),結果也將為整數。
l   測試2
運算元(Operand)即73直接補上.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百萬(Million10^6)為範例資料進行106 * 106 / 106 = 106運算測試,如下列兩組SQL語法所示: 

#
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
測試資料1000000,就其數值範圍其型態應歸為整數(int),int的數值範圍為-2^31 (-2,147,483,648)2^31-1(2,147,483,647)
l   測試1
SQLMillion*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 20052008於官方文件上所定義的SUM函數的「傳回類型」合併表列如下。

SQL SERVER 2005

SQL SERVER 2008
運算式結果
傳回類型
整數類別目錄
int
decimal 類別目錄 (p, s)
decimal(38, s)
money smallmoney 類別目錄
money
float real 類別目錄
float

運算式結果
傳回類型
tinyint
int
smallint
int
int
int
bigint
bigint
decimal 類別目錄 (p, s)
decimal(38, s)
money smallmoney 類別目錄
money
float real 類別目錄
float

對於SQL SERVER 2005(含以前)版本若運算結果為整數類別則均傳回int,當運算結果為bigint,而傳回類型卻仍定義為int,因而造成溢位錯誤,SQL SERVER 2008文件上說明此版本已修正此限制,但實際測試時卻仍發生相同問題。

函數引數(Argument問題
RADIANS函數角度(deg30度轉換為弧度(rad),傳入引數(Argument)將影響傳回結果,官方文件定義傳回資料型態類型與引數相同,此與一般概念所差異,使用上請多留意。測試如下所示。

測試

RADIANS語法
指令
結果
RADIANS(30)
0
RADIANS(30.0)
0.523598775598298790

RADIANS ( numeric_expression )

傳回類型
傳回與 numeric_expression 相同的類型

l   當傳入30.0(浮點數)時函數傳回值為0.5236(浮點數)
l   當傳入30(整數)時傳回則為0(整數)


綜合前述SQL SERVER中數學運算時可能發生溢位或捨位的問題,整理出下列說明。
1.  運算元兩者中擇高優先順序
當運算子為乘法時,因運算元均為整數而可能發生溢位的問題。當為除法時,會誤將商數當成一般運算結果,而發生捨位誤差的問題。當運算子均為相同優先順序時,運算會自左而右依序運算的特性,值得特別注意。
A
*
B
=
R

運算子


資料型態由AB兩者中擇高優先順序的資料型態。

2.  引數
對於許多函數如RADIANSDEGREES等,傳回資料型態是由引數所決定,因此,呼叫時需特別將引數轉換成輸出結果預期的資料型態。
RADIANS
(
A
)
=
R
函數

引數


資料型態由引數所決定

3.  運算式結果
SQL SERVER 2005(含)以前版本,許多函數運算結果若為整數類別時,回傳僅以int代表,當運算結果已達bigint範圍將造成溢位錯誤。雖2008版本官方文件說明已修正此限制,但實測上仍相同。
SUM
(
A
)
=
R
函數

引數


資料型態由運算式結果所決定,但回傳整數類別時僅以int代表

數學運算上許多不可預期的錯誤或難以除錯的問題,常來自於前述三個SQL SERVER的運算特性,開發或系統測試時需多加注意。

1 則留言:

  1. 謝謝您的解釋,非常的清楚,我有引用您的文章在我的個人部落格(https://iamswc.space),若有不妥,請告知,謝謝

    回覆刪除