Custom rounding in SQL - sql-server

How can I set rules for custom rounding in SQL?
I need set this rules:
if second number after decimal is 1-2 eg. 10,02 - round it to 0 result 10,00
if second number after decimal is 3-7 eg. 10,13 - round it to 5 result 10,15
if second number after decimal is 8-9 eg. 10,28 - round it to bigger number result 10,3
Can someone help me how to do this? Round function works from 0-5 and above 5 but how to do "custom rounding function" based on this rules?
Thanks in advance.

One possible approach is the following statement:
SELECT
[Number],
CASE
WHEN ([Number] * 100) % 10 BETWEEN 3 AND 7 THEN ROUND([Number] + 0.02, 1) - 0.05
ELSE ROUND([Number] + 0.02, 1)
END [RoundedNumber]
FROM (VALUES (10.00), (10.01), (10.12), (10.13), (10.14), (10.28), (10.29)) v ([Number])
Result:
Number RoundedNumber
10.00 10.00
10.01 10.00
10.12 10.10
10.13 10.15
10.14 10.15
10.28 10.30
10.29 10.30

You can create your own function to use your custom rounding rules:
CREATE FUNCTION CustomRound(#num DECIMAL(18,2))
RETURNS DECIMAL(18,2)
AS
BEGIN
RETURN ROUND(#num, 1, 1) +
CASE WHEN (#num - ROUND(#num, 1, 1)) * 100 BETWEEN 1 AND 2 THEN 0
WHEN (#num - ROUND(#num, 1, 1)) * 100 BETWEEN 3 AND 7 THEN 0.05
WHEN (#num - ROUND(#num, 1, 1)) * 100 BETWEEN 8 AND 9 THEN 0.1
END
END
This function truncates the decimal number after first decimal place and add depending on the second decimal place a value to "round" as needed.
You can use the above function like this:
SELECT dbo.CustomRound(10.12) -- 10.10
SELECT dbo.CustomRound(10.02) -- 10.00
SELECT dbo.CustomRound(10.13) -- 10.15
SELECT dbo.CustomRound(10.28) -- 10.30
demo on dbfiddle.uk
You can also extend this function to be more dynamic using the custom rounding:
CREATE FUNCTION CustomRound(#num DECIMAL(18,6), #precision INT)
RETURNS DECIMAL(18,6)
AS
BEGIN
DECLARE #prec INT = IIF(#precision > 0, #precision - 1, 0);
RETURN ROUND(#num, #prec, 1) +
CASE WHEN (#num - ROUND(#num, #prec, 1)) * POWER(10, #precision) BETWEEN 1 AND 2 THEN 0.0 / POWER(10, #precision)
WHEN (#num - ROUND(#num, #prec, 1)) * POWER(10, #precision) BETWEEN 3 AND 7 THEN 5.0 / POWER(10, #precision)
WHEN (#num - ROUND(#num, #prec, 1)) * POWER(10, #precision) BETWEEN 8 AND 9 THEN 10.0 / POWER(10, #precision)
END
END
You can use this function like this:
SELECT dbo.CustomRound(10.12, 2) -- 10.10
SELECT dbo.CustomRound(10.02, 2) -- 10.00
SELECT dbo.CustomRound(10.13, 2) -- 10.15
SELECT dbo.CustomRound(10.28, 2) -- 10.30
-- or
SELECT dbo.CustomRound(10.102, 3) -- 10.100
SELECT dbo.CustomRound(10.002, 3) -- 10.000
SELECT dbo.CustomRound(10.103, 3) -- 10.105
SELECT dbo.CustomRound(10.208, 3) -- 10.210
demo on dbfiddle.uk

You can define your custome funtion and use it for ever
DECLARE #NUM REAL =10.01;
DECLARE #RESULT NVARCHAR(MAX)
IF #NUM=ROUND(#NUM,0)
BEGIN
SET #RESULT=#NUM
END
ELSE
BEGIN
DECLARE #NUM_STR VARCHAR(MAX)=CAST(#NUM AS VARCHAR(MAX)) -- CONVERT TO VARCHAR
DECLARE #LEFT VARCHAR(MAX)= CASE WHEN #NUM>=0 THEN FLOOR(#NUM) ELSE CEILING(#NUM) END -- GET LEFT SIDE NUMBERS
DECLARE #RIGHT VARCHAR(MAX)= REPLACE(#NUM_STR,CONCAT(#LEFT,'.'),'') -- GET RIGHT SIDE NUMBERS
... -- YOUR ROLES
END

You can multiply by 20, then round to the whole number and divide by 20 again.
e.g.
SELECT ROUND(myNum * 20,0) / 20.0
FROM myTable;

Related

How to convert HHMMSS to seconds using T-SQL

SQL server table msdb.dbo.sysjobhistory returns run_time and run_duration as INTEGER value formatted as HHMMSS.
How to convert it to seconds?
Example:
163135 (16:31:35) becomes 59495 (seconds)
Meanwhile I figured out this formula:
SELECT DATEDIFF(SECOND, '00:00:00', FORMAT(run_duration, '00:00:00'))
FROM msdb.dbo.sysjobhistory
You can use modulo and integer division to separate the hours, minutes, and seconds, multiply by number of seconds in each result, then sum.
DECLARE #hms int = 163135;
SELECT #hms / 10000 * 3600
+ #hms % 10000 / 100 * 60
+ #hms % 100;
59495
To use this as a view, it's really not any different:
CREATE VIEW dbo.viewname
AS
SELECT <other cols>, run_duration,
run_duration_s = run_duration / 10000 * 3600
+ run_duration % 10000 / 100 * 60
+ run_duration % 100
FROM msdb.dbo.sysjobhistory
WHERE ...
If you don't like math so much, you can treat it like a string:
DECLARE #hms int = 163135;
DECLARE #s char(6) = RIGHT(CONCAT('000000', #hms), 6);
SELECT LEFT(#s, 2) * 60 * 60
+ SUBSTRING(#s, 3, 2) * 60
+ RIGHT(#s, 2);
59495
However, this latter solution may need some tweaking if you could have durations > 99 hours, since now the string will be 7 digits. Maybe safer to use:
DECLARE #hms int = 163135;
DECLARE #s char(24) = RIGHT(CONCAT(REPLICATE('0',24), #hms), 24);
SELECT LEFT(#s, 20) * 60 * 60
+ SUBSTRING(#s, 21, 2) * 60
+ RIGHT(#s, 2);
24 is a ludicrous example, but safe. The job would have had to start in 1990 to hit 10 digits today.

Add some specific value in each digit of number in SQL [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I need the formula for calculating some number
(1) Add 3 in each digit of the particular number
(2) If we get the result after addition in two digits like 10,11,12 etc then consider only last digit
For example if number would be 564843530 then enter code here
New number would be 897176023.
Solution:
DECLARE #number int
SET #number = 564843530;
WITH DigitsTable AS
(
SELECT 0 AS Power10, ABS(#Number) AS Number
UNION ALL
SELECT Power10 + 1, Number / 10
FROM DigitsTable
WHERE Number > 10
)
SELECT SUM((((Number % 10) + 3) % 10) * POWER(10, Power10)) AS NewNumber
FROM DigitsTable
OPTION (MAXRECURSION 0);
Output:
897176863
Explanation:
This answer is based on one recursive CTE which returns each current digit, new digit and multiplier for calculation:
DECLARE #number int
SET #number = 564843530;
WITH DigitsTable AS
(
SELECT 0 AS Power10, ABS(#Number) AS Number
UNION ALL
SELECT Power10 + 1, Number / 10
FROM DigitsTable
WHERE Number > 10
)
SELECT
Power10,
Number % 10 AS Digit,
((Number % 10) + 3) % 10 AS NewDigit,
POWER(10, Power10) AS Multiplier
FROM DigitsTable
OPTION (MAXRECURSION 0);
Output from recursive CTE:
Power10 Digit NewDigit Multiplier
0 0 3 1
1 3 6 10
2 5 8 100
3 3 6 1000
4 4 7 10000
5 8 1 100000
6 4 7 1000000
7 6 9 10000000
8 5 8 100000000
Ty this:
SELECT
(
SELECT RIGHT(SUBSTRING(DS.[number], v.[number] + 1, 1) + 3, 1)
FROM
(
SELECT '564843530'
) DS ([number])
INNER JOIN [master]..spt_values v on v.[number] < LEN(DS.[number])
WHERE V.[type] = 'P'
ORDER BY v.[number]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)');
The algorithm is simple:
split the string to chars (in your case numbers)
add 3 to each number
using RIGHT get the first right char only
concatenate the numbers
This should work on older editions. If you are using SQL Server 2016 you can use functions like STRING_SPLIT and STRING_AGG in SQL Server 2017.
For older version you can look for SQL CLR function in order to write your own functions for splitting and concatenating. For example, in my system I can do:
Check the following link if interested in SQL CLR.
Assume that the example value supplied by the OP is wrong (as, per my comment "Shouldn't 564843530 become 897176863? How is 5 + 3 = (1)0? and 3 + 3 = 2?"), then this is my method, which makes use of NGrams8k:
WITH VTE AS (
SELECT 564843530 AS SomeNumber),
Split AS(
SELECT V.SomeNumber,
NG.token,
NG.position
FROM VTE V
CROSS APPLY dbo.NGrams8k(V.SomeNumber,1) NG)
SELECT SomeNumber AS OldNumber,,
CONVERT(bigint,(SELECT token + 3
FROM Split x
WHERE S.SomeNumber = x.SomeNumber
ORDER BY x.position
FOR XML PATH(''))) AS NewNumber
FROM Split S
GROUP BY S.SomeNumber;
Try this
DECLARE #n int, #inc int;
SELECT #n = 564843530, #inc = 3;
WITH t1(n, m, k, p)
AS (
SELECT #n AS n, CAST(0 AS int) AS m, CAST(0 AS int) AS k, CAST(0 AS bigint) AS p
UNION ALL
SELECT n / 10 AS n, n - round(n, -1, 1) AS m, k + 1 AS k, CAST(power(10, k) AS bigint) AS p
FROM t1 WHERE n > 0
)
SELECT SUM((CASE WHEN m > 10 - #inc THEN m - 10 + #inc ELSE m + #inc END) * p)
FROM t1
Result
897176863

Convert number of minutes to hh:mm

I have a column in a table that stores the number of minutes as a numeric(18,4) field named [course_access_minutes].
The stored values come from a blackboard database and look like this:
0.0500
0.0667
0.3667
up to
314.0833
625.8167
How do I convert these to time hh:mm, I've had a good look at the database documentation and all I can find is
course_access_minutes numeric(18,4) This is the number of minutes that the user accesses this course in total during this login session.
Can I assume that I can make a direct conversion from minutes into hours? I think I will take any values below 1 as 0 minutes. What is the best way to do this in SQL? Thanks in advance for your help.
Try this
SELECT CONVERT(varchar, DATEADD(s, 625.8167 * 60, 0), 108)
If the duration is longer than 24 hours you can use this
SELECT CONVERT(varchar, CAST(1877.4501 * 60 AS int) / 3600)
+ RIGHT(CONVERT(varchar, DATEADD(s, 1877.4501 * 60, 0), 108), 6)
You could use FLOOR like this
DECLARE #SampleData AS TABLE
(
Minutes numeric(18,4)
)
INSERT INTO #SampleData
VALUES
( 0.0500),
( 1.0500),
( 30.0500),
( 80.0500),
( 314.0833),
( 625.8167)
SELECT CONCAT(floor(sd.Minutes/60),':', CASE WHEN sd.Minutes - floor(sd.Minutes/60)*60 < 1 THEN '0'
ELSE FLOOR(sd.Minutes - floor(sd.Minutes/60)*60 )
END) AS hours
FROM #SampleData sd
Returns
hours
0:0
0:1
0:30
1:20
5:14
10:25
WITH _Samples AS (
SELECT CONVERT(numeric(18, 4), 0.0500) [course_access_minutes]
UNION ALL SELECT 0.0667
UNION ALL SELECT 0.3667
UNION ALL SELECT 314.0833
UNION ALL SELECT 625.8167
)
SELECT
S.course_access_minutes,
-- split out the number
FLOOR(S.course_access_minutes / 60) [hours],
FLOOR(S.course_access_minutes % 60) [minutes],
FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60) [seconds],
-- to a string
CONVERT(varchar(10), FLOOR(S.course_access_minutes / 60))
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR(S.course_access_minutes % 60)), 2)
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60)), 2) [time_string],
-- You could consider converting to the time data type if the values will never exceed the limit
-- time supports 00:00:00.0000000 through 23:59:59.9999999
-- 0 through 1439.9833333 ... 23 * 60 = 1380 + 59 = 1439 + (59 / 60) = 1439.9833333
-- (see: https://learn.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
CONVERT(time,
CONVERT(varchar(10), FLOOR(S.course_access_minutes / 60))
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR(S.course_access_minutes % 60)), 2)
+ ':' + RIGHT('00' + CONVERT(varchar(10), FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60)), 2)
) [time]
FROM
_Samples S
(It wouldn't be difficult to further this idea and split out the fractional seconds as well.)
Which yields:
course_access_minutes hours minutes seconds time_string time
---------------------- ------ -------- -------- ------------ ----------------
0.0500 0 0 3 0:00:03 00:00:03.0000000
0.0667 0 0 4 0:00:04 00:00:04.0000000
0.3667 0 0 22 0:00:22 00:00:22.0000000
314.0833 5 14 4 5:14:04 05:14:04.0000000
625.8167 10 25 49 10:25:49 10:25:49.0000000
Note that this is going to be like Greg's answer, but I wanted to explain and simplify it.
You have minutes, so dividing them by 60 and flooring it (removing the decimal) gives the hours (without the minutes).
If you take the total minutes again, and remove (mod it by) the floored hours - which requires conversion to minutes by multiplying by 60 - you are left with the remaining minutes by essentially just finding out what is left after taking away that many groups of sixties:
SELECT FLOOR(course_access_minutes / 60) as Hours,
(FLOOR(course_access_minutes) % 60) as Minutes
FROM MyTable
If you want the decimal to appear for the amount of minute fractions (you want the seconds to appear, in decimal form), remove FLOOR.
If you want seconds in real numbers, keep FLOOR and use what Greg had: FLOOR((S.course_access_minutes - FLOOR(S.course_access_minutes)) * 60) for seconds. Be careful with the parenthesis, though, because you can end up accidentally flooring your decimaled minutes and get 0, and then 0*60 is zero:
FLOOR(
(
course_access_minutes -
FLOOR(course_access_minutes)
) * 60
) as Seconds

SQL Adding integer strings with zero values

I am trying to add strings which are integers. I have 201404 as input and I need it to be converted to 201503 so the only way to do this is to increase the year (2014) by 1 and decrease the month 02 by 1.
I have tried the below but the leading zero in the month does not seem to preserve:
DECLARE #YearMonth INT = 201404
, #left INT = 0
, #right INT = 0
SET #YearMonth = CAST(#YearMonth AS VARCHAR(6))
SET #left = CAST(LEFT(#YearMonth, 4) + 1 AS VARCHAR(MAX))
SET #right = RIGHT(#YearMonth, 2) - 1
SET #right = CAST(#right AS VARCHAR(2))
SET #right = RIGHT(('0' + CAST(#right AS VARCHAR(2))), 2)
PRINT #left
PRINT RIGHT('0' + LTRIM(RTRIM(#right)), 6)
Dealing with integer YYYYMM format can be difficult when adding and subtracting months. One method is to convert to a number of months, and then convert back to the format. So, this converts the value to a number of months
select (#YearMonth / 100) * 12 + (#YearMonth % 100)
Then we can add a number, such as 11 and convert back to the integer format:
select (( (#YearMonth / 100) * 12 + (#YearMonth % 100) + 11) / 12) * 100 +
( (#YearMonth / 100) * 12 + (#YearMonth % 100) + 11) % 12)
) as yyyymm
Another method that might be simpler is to use date arithmetic:
select dateadd(11, month, cast(#YearMonth as varchar(255)) + '01')
This returns a date. You can convert it back to the number as:
select (year(dateadd(11, month, cast(#YearMonth as varchar(255)) + '01')) * 100 +
month(dateadd(11, month, cast(#YearMonth as varchar(255)) + '01'))
) as yyyymm
Use REPLICATE
replicate('0', 2 - len(#right)) + #right
Just ran this:
DECLARE #YearMonth INT = 201404;
SELECT CONVERT(VARCHAR(6), DATEPART(YEAR, T.Data) + 1) + RIGHT(100 + DATEPART(MONTH, T.Data) -1, 2)
FROM (VALUES (CONVERT(VARCHAR(8), #YearMonth) + '01')) AS T(Data);
Result:
201503
It's going to pick month number and add 100 to it and then pick 2 right chars from it, so for instance you got 4, it becomes 104 and then RIGHT function picks last 2 characters, which are 04.
Checked with other params, seems fine:
DECLARE #YearMonth INT = 201411;
SELECT CONVERT(VARCHAR(6), DATEPART(YEAR, T.Data) + 1) + RIGHT(100 + DATEPART(MONTH, T.Data) -1, 2)
FROM (VALUES (CONVERT(VARCHAR(8), #YearMonth) + '01')) AS T(Data);
Result:
201510
I would convert implicitly to date, add 11 months and then format back as a string. The integer conversion would be implicit as well.
select format(dateadd(month, 11, str(#YearMonth) + '01'), 'yyyyMM')

Round *UP* to the nearest 100 in SQL Server

Is it possible to easily round a figure up to the nearest 100 (or 1000, 500, 200 etc.) in SQL Server?
So:
720 -> 800
790 -> 800
1401 -> 1500
The following should work. After reading your question, I'm not exactly sure what you want 100 to return. For this 100 returns 100.
select floor((X + 99) / 100) * 100;
This gives the following results:
0 -> 0
1 -> 100
99 -> 100
100 -> 100
101 -> 200
For rounding Up to the nearest thousand, try the following:-
select round(YourValue, -3)
One option would be to use the CEILING() function like this:
SELECT CEILING(#value/100.0) * 100
You may need to convert your value to a decimal first depending on its type.
Use CEILING function to round a figure up
DECLARE #Number DECIMAL, #RoundUp DECIMAL
SET #RoundUp = 100
SET #Number = 720
SELECT CEILING(#Number/#RoundUp)*#RoundUp
Try this:
select round(#value , -2);
It is very simple to round a number to any multiple of nearest 10 by using simply the ROUND function
for ex:
SELECT ROUND(number/1000,2)*1000
This will give you the nearest thousandth value.
This will work for the values with decimal also.
select floor((ceiling (#value) + 99) / 100) * 100;
There's no native function that will do this, but there are any number of simple math tricks that will. An example:
DECLARE #Foo int
SET #Foo = 720
print #Foo
print (#Foo + 100) % 100
PRINT #Foo - (#Foo + 100) % 100
You can use this code, assuming your amount is an int. If not you will need to cast, so you get integer division.
If amount % 100 != 0 Then
roundedAmount = ((amount / 100) * 100) + 100
Else
roundedAmount = amount
You might want to package this into a user defined function.
A generic solution - Use MOD to find the last 100th place and then add 100 to the result.
select (720 - MOD(720,100)) + 100 from dual;
If you need the next 80th place, just replace any "100" with "80".
In addition to Gray's answer,
I'd use the following inline function:
CREATE FUNCTION dbo.udf_RoundNearest
(
#Number bigint,
#RoundNearest bigint,
#Direction int
)
RETURNS TABLE AS
RETURN
SELECT CASE WHEN #RoundNearest>=#Number THEN #Number
ELSE
(
(#Number + CASE
WHEN #Direction = 0 --Round Down
THEN 0
ELSE CASE WHEN #Number % #RoundNearest = 0 THEN 0 ELSE #RoundNearest END
END) / #RoundNearest) * #RoundNearest
END Number
Parameter Definition:
#Number - the number you need to round
#RoundNearest 10th, 100th , 1000th etc
#Direction 0-> round down, 1-> round up
using the function:
SELECT * FROM dbo.udf_RoundNearest (1965,100,1) --> 2000
SELECT * FROM dbo.udf_RoundNearest (1359,100,0) --> 1300
SELECT * FROM dbo.udf_RoundNearest (1999,10,0) --1990
SELECT * FROM dbo.udf_RoundNearest (80,100,0) --> 80 (if the #number parameter is less or equal the #RoundNearest parameter the result will be the #number itself
it can also be used as apply it versus a table
such as:
;with tmp (Value) as
(select 1236 union all select 6584 union all select 9999)
select t.*, fn.Number
from tmp t
cross apply dbo.udf_RoundNearest (Value,100,0) fn
/*Result Set
Value Number
1236 1200
6584 6500
9999 9900*/
This worked fine for me.
Round(#value/100, 0) * 100
It works fine for integer value:
#roundUpValue = ((#intValue / 1000) + 1) * 1000
#roundDownValue = (#intValue / 1000) * 1000
For example
declare #intValue as int = 1934
select ((#intValue / 1000) + 1) * 1000 as roundUp
select (#intValue / 1000) * 1000 as roundDown
If you want to round up to the nearest 500 then
select ((#intValue / 500) + 1) * 500 as roundUp
Select round(value/100,0)*100
Whatever number you want to round to, just post that instead of 100 here.
i have create a function in mssql it can help you
CREATE function dbo.roundup
(
#numbr decimal(18,2),
#frac decimal(18,2)
)
RETURNS decimal(18,2)
AS
BEGIN
DECLARE #result decimal(18,2)
set #result = ceiling(#numbr/#frac)*#frac
RETURN #result
END
GO

Resources