بهینهسازی گرد کردن اعداد در SQL Server: راهنمای کامل تابع ROUND برای دقت بالا در T-SQL
گرد کردن اعداد در محاسبات مالی یک کار بسیار حساس و دقیق است. به عنوان مثال، اگر در حال محاسبه مالیات بر فروش یا بهره هستید، بسیار مهم است که تابع گرد کننده به درستی و به صورت پایدار عمل کند. در SQL Server، چندین گزینه برای گرد کردن اعداد به نزدیکترین پنی (یا دو رقم اعشار) وجود دارد که در ادامه به بررسی آنها میپردازیم.
تابع ROUND در SQL Server رایجترین تابع برای گرد کردن اعداد است. ساختار این تابع به شکل زیر است:
ROUND ( numeric_expression , length [ , function ] )
در این ساختار:
- numeric_expression: عبارت عددی مورد نظر برای گرد کردن.
- length: عددی است که دقت گرد کردن را تعیین میکند (مثلاً 2 برای دو رقم اعشار).
- function: یک پارامتر اختیاری است. اگر 0 باشد (یا مشخص نشود)، تابع ROUND به نزدیکترین عدد گرد میکند. اگر مقداری غیرصفر باشد، تابع ROUND عدد را کوتاه میکند (truncates) و گرد نمیکند.
یکی از مسائلی که گاهی با تابع ROUND پیش میآید، نحوه برخورد آن با اعدادی است که به .5 ختم میشوند. SQL Server این مقادیر را به سمت بالا و دور از صفر گرد میکند. به عنوان مثال، اگر عدد 1.255 را به دو رقم اعشار گرد کنیم، 1.26 میشود. اما اگر -1.255 را به دو رقم اعشار گرد کنیم، -1.26 میشود.
SELECT ROUND(1.255, 2) AS PositiveNumber, ROUND(-1.255, 2) AS NegativeNumber;
در اینجا مثالی آورده شده که نحوه عملکرد تابع ROUND در SQL Server را با و بدون پارامتر function
نشان میدهد. توجه کنید که وقتی پارامتر function
برابر با 1 باشد، عدد را به جای گرد کردن، کوتاه میکند (truncate).
SELECT ROUND(1.255, 2, 0) AS RoundedNumber, ROUND(1.255, 2, 1) AS TruncatedNumber;
گزینه دیگر برای گرد کردن اعداد به نزدیکترین پنی در SQL Server، استفاده از توابع CAST یا CONVERT است. این روش با CAST یا CONVERT کردن مقدار عددی به یک نوع داده DECIMAL یا NUMERIC با دقت و مقیاس مشخص کار میکند. به عنوان مثال، برای گرد کردن به دو رقم اعشار، از DECIMAL(P,2)
استفاده میشود که P تعداد کل ارقام را نشان میدهد. این روش به طور موثری عدد را به دقت مشخص شده گرد میکند و مقادیر .5 را نیز مشابه تابع ROUND به سمت بالا گرد میکند.
SELECT CAST(1.255 AS DECIMAL(10, 2)) AS CastedNumber, CONVERT(DECIMAL(10, 2), 1.255) AS ConvertedNumber;
گزینه سوم، اگر توابع ROUND و CAST / CONVERT نیازهای شما را برآورده نمیکنند، ایجاد یک تابع تعریف شده توسط کاربر (UDF) برای گرد کردن است. این روش به شما کنترل کاملی بر نحوه گرد کردن اعداد میدهد و به شما امکان میدهد قوانین تجاری خاصی را پیادهسازی کنید. به عنوان مثال، میتوانید تابعی ایجاد کنید که همیشه مقادیر .5 را دور از صفر گرد کند یا به سمت نزدیکترین عدد زوج (گرد کردن بانکی) ببرد.
CREATE FUNCTION dbo.udf_RoundToNearestPenny (@Value DECIMAL(18, 4))
RETURNS DECIMAL(18, 2)
AS
BEGIN
RETURN ROUND(@Value, 2);
END;
پس از ایجاد UDF، میتوانید آن را مانند هر تابع دیگری در کوئریهای خود استفاده کنید:
SELECT dbo.udf_RoundToNearestPenny(1.255) AS CustomRoundedNumber;
هنگام انتخاب یک روش گرد کردن، عملکرد میتواند یک عامل مهم باشد، به خصوص با مجموعه دادههای بزرگ. به طور کلی، توابع داخلی مانند ROUND، CAST و CONVERT برای عملکرد به شدت بهینه شدهاند، زیرا در سطوح پایینتر در SQL Server پیادهسازی شدهاند. UDF های سفارشی، در حالی که انعطافپذیری ارائه میدهند، ممکن است به دلیل مکانیسمهای فراخوانی تابع، سربار (overhead) ایجاد کنند و احتمالاً برای عملیات با حجم بالا کندتر باشند. با این حال، برای اکثر برنامهها، تفاوت عملکرد ممکن است ناچیز باشد.
برای نشان دادن تفاوت عملکرد، میتوانیم یک جدول آزمایشی با تعداد زیادی مقدار اعشاری ایجاد کنیم و سپس زمان اجرای هر روش را مقایسه کنیم.
IF OBJECT_ID('tempdb..#TestNumbers') IS NOT NULL
DROP TABLE #TestNumbers;
CREATE TABLE #TestNumbers (
ID INT IDENTITY(1,1) PRIMARY KEY,
Value DECIMAL(18, 4)
);
-- Insert 1 million random numbers
INSERT INTO #TestNumbers (Value)
SELECT
CAST(RAND(CHECKSUM(NEWID())) * 1000 AS DECIMAL(18, 4)) +
CAST(RAND(CHECKSUM(NEWID())) AS DECIMAL(18, 4)) *
CASE WHEN RAND(CHECKSUM(NEWID())) > 0.5 THEN 1 ELSE -1 END
FROM
sys.all_columns a
CROSS JOIN
sys.all_columns b
WHERE
a.object_id < 100 AND b.object_id < 100;
اکنون، زمانهای اجرا را مقایسه میکنیم:
SET STATISTICS TIME ON;
GO
-- Using ROUND
SELECT COUNT(*) FROM #TestNumbers WHERE ROUND(Value, 2) IS NOT NULL;
GO
-- Using CAST
SELECT COUNT(*) FROM #TestNumbers WHERE CAST(Value AS DECIMAL(18, 2)) IS NOT NULL;
GO
-- Using UDF (ensure UDF is created first)
-- SELECT COUNT(*) FROM #TestNumbers WHERE dbo.udf_RoundToNearestPenny(Value) IS NOT NULL;
-- GO
SET STATISTICS TIME OFF;
GO
“`