การทำความเข้าใจความแม่นยำและขนาดในบริบทของการดำเนินการทางคณิตศาสตร์
Let 's ทำลายลงนี้และใช้เวลามองใกล้ที่รายละเอียดของการแบ่งผู้ประกอบการทางคณิตศาสตร์ นี่คือสิ่งที่ MSDN พูดถึงเกี่ยวกับประเภทผลลัพธ์ของตัวดำเนินการหาร :
ประเภทผลลัพธ์
ส่งคืนชนิดข้อมูลของอาร์กิวเมนต์ที่มีลำดับความสำคัญสูงกว่า สำหรับข้อมูลเพิ่มเติมโปรดดูที่ชนิดของข้อมูลเป็นผู้นำ (Transact SQL)
หากการหารจำนวนเต็มหารด้วยตัวหารจำนวนเต็มผลลัพธ์จะเป็นจำนวนเต็มที่มีส่วนที่เป็นเศษส่วนของผลลัพธ์ที่ถูกตัดทอน
เรารู้ว่าเป็น@big_number
DECIMAL
สิ่งที่ชนิดข้อมูล SQL Server ไม่โยน1
เป็น? INT
มันได้ปลดเปลื้องไปยัง เราสามารถยืนยันสิ่งนี้ได้ด้วยความช่วยเหลือของSQL_VARIANT_PROPERTY()
:
SELECT
SQL_VARIANT_PROPERTY(1, 'BaseType') AS [BaseType] -- int
, SQL_VARIANT_PROPERTY(1, 'Precision') AS [Precision] -- 10
, SQL_VARIANT_PROPERTY(1, 'Scale') AS [Scale] -- 0
;
สำหรับการเตะเรายังสามารถแทนที่1
ในบล็อกรหัสต้นฉบับด้วยค่าที่พิมพ์อย่างชัดเจนเช่นDECLARE @one INT = 1;
และยืนยันว่าเราได้ผลลัพธ์เดียวกัน
ดังนั้นเราจึงมีและDECIMAL
INT
เนื่องจากDECIMAL
มีความสูงชนิดข้อมูลมีความสำคัญกว่าเรารู้ว่าการส่งออกของส่วนของเราจะถูกส่งไปยัง INT
DECIMAL
ดังนั้นปัญหาอยู่ที่ไหน
ปัญหาเกิดขึ้นกับสเกลของDECIMAL
เอาต์พุต นี่คือตารางของกฎเกี่ยวกับวิธีที่ SQL Server กำหนดความแม่นยำและขนาดของผลลัพธ์ที่ได้จากการดำเนินการทางคณิตศาสตร์:
Operation Result precision Result scale *
-------------------------------------------------------------------------------------------------
e1 + e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 - e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 * e2 p1 + p2 + 1 s1 + s2
e1 / e2 p1 - s1 + s2 + max(6, s1 + p2 + 1) max(6, s1 + p2 + 1)
e1 { UNION | EXCEPT | INTERSECT } e2 max(s1, s2) + max(p1-s1, p2-s2) max(s1, s2)
e1 % e2 min(p1-s1, p2 -s2) + max( s1,s2 ) max(s1, s2)
* The result precision and scale have an absolute maximum of 38. When a result
precision is greater than 38, the corresponding scale is reduced to prevent the
integral part of a result from being truncated.
และนี่คือสิ่งที่เรามีสำหรับตัวแปรในตารางนี้:
e1: @big_number, a DECIMAL(38, 0)
-> p1: 38
-> s1: 0
e2: 1, an INT
-> p2: 10
-> s2: 0
e1 / e2
-> Result precision: p1 - s1 + s2 + max(6, s1 + p2 + 1) = 38 + max(6, 11) = 49
-> Result scale: max(6, s1 + p2 + 1) = max(6, 11) = 11
ต่อความคิดเห็นดอกจันบนโต๊ะข้างต้นที่มีความแม่นยำสูงสุดDECIMAL
สามารถมี 38 ดังนั้นความแม่นยำของผลลัพธ์ของเราจึงลดลงจาก 49 เป็น 38 และ "ลดขนาดที่สอดคล้องกันเพื่อป้องกันไม่ให้ส่วนที่สำคัญของผลลัพธ์ถูกตัดทอน" ยังไม่ชัดเจนจากความคิดเห็นนี้ว่าระดับลดลงได้อย่างไร แต่เรารู้สิ่งนี้:
ตามสูตรการคำนวณในตารางที่ขั้นต่ำขนาดที่เป็นไปได้คุณสามารถมีหลังจากหารสองDECIMAL
คือ 6
ดังนั้นเราจะได้ผลลัพธ์ต่อไปนี้:
e1 / e2
-> Result precision: 49 -> reduced to 38
-> Result scale: 11 -> reduced to 6
Note that 6 is the minimum possible scale it can be reduced to.
It may be between 6 and 11 inclusive.
วิธีนี้อธิบายถึงการล้นทางคณิตศาสตร์
ตอนนี้คำตอบนั้นชัดเจน:
ผลลัพธ์ของแผนกของเราถูกส่งไปDECIMAL(38, 6)
และDECIMAL(38, 6)
ไม่สามารถถือ 10 37ได้
ด้วยสิ่งนี้เราสามารถสร้างแผนกอื่นที่ประสบความสำเร็จได้ด้วยการทำให้แน่ใจว่าผลลัพธ์สามารถเข้าDECIMAL(38, 6)
กันได้:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
PRINT @big_number / @one_million;
ผลลัพธ์คือ:
10000000000000000000000000000000.000000
สังเกต 6 ศูนย์หลังจุดทศนิยม เราสามารถยืนยันประเภทข้อมูลของผลลัพธ์ได้DECIMAL(38, 6)
โดยใช้SQL_VARIANT_PROPERTY()
ด้านบน:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
SELECT
SQL_VARIANT_PROPERTY(@big_number / @one_million, 'BaseType') AS [BaseType] -- decimal
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Precision') AS [Precision] -- 38
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Scale') AS [Scale] -- 6
;
วิธีแก้ปัญหาอันตราย
ดังนั้นเราจะแก้ไขข้อ จำกัด นี้ได้อย่างไร
แน่นอนว่าขึ้นอยู่กับสิ่งที่คุณทำการคำนวณเหล่านี้ ทางออกหนึ่งที่คุณสามารถข้ามไปได้ทันทีคือการแปลงตัวเลขของคุณเป็นFLOAT
สำหรับการคำนวณแล้วแปลงกลับเป็นDECIMAL
เมื่อคุณทำเสร็จแล้ว
ที่อาจทำงานภายใต้สถานการณ์บางอย่าง แต่คุณควรระมัดระวังที่จะเข้าใจว่าสถานการณ์เหล่านั้นคืออะไร ดังที่เราทุกคนรู้ว่าการแปลงตัวเลขไปและกลับจากที่FLOAT
เป็นอันตรายและอาจให้ผลลัพธ์ที่ไม่คาดคิดหรือไม่ถูกต้อง
ในกรณีของเราการแปลง 10 37ไปเป็นจากนั้นFLOAT
จะได้ผลลัพธ์ที่ผิดธรรมดา:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @big_number_f FLOAT = CAST(@big_number AS FLOAT);
SELECT
@big_number AS big_number -- 10^37
, @big_number_f AS big_number_f -- 10^37
, CAST(@big_number_f AS DECIMAL(38, 0)) AS big_number_f_d -- 9999999999999999.5 * 10^21
;
และคุณมีมัน แบ่งอย่างระมัดระวังลูก ๆ ของฉัน