How to do SQL Server rounding digit by digit? - sql

A consultant has described a type of rounding I need to use in a financial application.
The following value: 0.01488
needs to be rounded in steps like this. We round each digit at a time,
0.0148 -> 0.015
0.015 -> 0.02
Thus the result is 0.02 (2 cents).
But if we did normal rounding to 2 dp the value 0.0148 would round to 0.01.
What is the name of this rounding? And how can I do it with SQL Server?
Update:
My example above is an arbitrary example. The starting value might be 0.15436798, i.e. any number of decimal places. It is related to the result of a previous multiplication. In that case I would need to round more times.

You can create a scalar function to do this job.
like:
CREATE function [dbo].[fn_roundx]( #num FLOAT,#round INT)
RETURNS FLOAT
AS
BEGIN
DECLARE #count INT=10;
WHILE #count > = #round
BEGIN
SET #num = ROUND(#num,#count);
SET #count=#count-1;
END;
RETURN #num
END
And then use it as per your conveneince for any depth of rounding you want?
For example
DECLARE #num FLOAT=0.014887;
SELECT #num AS ActualNumber,dbo.fn_roundx(#num,2) AS RoundedNumber
or
DECLARE #num FLOAT=0.014887;
SELECT #num AS ActualNumber,dbo.fn_roundx(#num,3) AS RoundedNumber
Sounds good?

How about something like following?
DECLARE #num decimal(20,5)=0.014880;
DECLARE #count INT=5;
DECLARE #round INT=2;
SELECT #num value;
WHILE #count > = #round
BEGIN
SET #num = ROUND(#num,#count);
SET #count=#count-1;
END;
SELECT #num value;
with following output..
for any number up to any number of round you want?

Use ROUND function twice
First time pass length as 3 to round 0.01488 to 0.01500
SELECT Round(0.01488, 3) -- 0.01500
On top of it pass length as 2 to round 0.01500 to 0.02000
SELECT Round(0.01500, 2) -- 0.02000
finally it should be something like this
DECLARE #num NUMERIC(16, 5) = 0.01488
SELECT Round(Round(#num, 3), 2)

Try this,
select round(round(0.01500,3),2)
select round(round(0.01488,3),2)

Related

Find next greater number with zeros in sql number

I'm looking for a way to find next greater number starting by 1 and followed by zeros in Microsoft SQL. Numbers could vary in digits. ie:
Query: 9856, Result after procedure: 10000
Query: 98999, Result after procedure: 100000
Thanks.
EDIT: There is no chance of having negative numbers. This is a calculation for a energy meter. For example, numbers can go up to 99999 or 999999 or 9999999. When energy overcome that number, it will start again at 0. So I can't read what energy has been used in that period. To know it, I need to calculate the number as asked, then perform some basic maths.
There is no need for knowing what is going on on 10, 100, etc, because of the nature of the operation. It will only be used when the above escenario happend.
I don't know why you require or mathematically any other formula can be implemented. but technically that can be achieved as follows
DECLARE #count INT
SET #count = 1000
DECLARE #result INT
SET #result = CASE WHEN #count%10 = 0 THEN #count ELSE CAST('1'+REPLICATE('0',LEN(#count)) AS INT) end
SELECT #result
This works for positive numbers (numbers greater than zero):
select power(10, ceiling( log10(the_number) )) from mytable;
In case the number is already a power of ten (1, 10, 100, ...) , the number itself is returned.
You can do this with just arithmetic operations:
select power(10, floor(log(v.n - 0.1, 10)) + 1)
from (values (1), (10), (8), (9982), (124)) v(n)
This is a fairly crude way of doing it, however it does work. The query basically looks at the number of digits your number has, assumes the next integer you want starts with a 1 and then adds the relevant number of 0's to it.
Note this only looks for the next increment and does not round down.
Also for 10 you will get 100 and for 1000 you will get 10000.
declare #number int = 98999;
declare #len int = len(#number);
declare #stringtoreturn nvarchar(200)='1';
declare #runs int = 1;
while #runs<=#len
begin
select #stringtoreturn = #stringtoreturn + '0';
select #runs=#runs+1;
end
select #stringtoreturn;

How to order by postcode?

I have a small report that needs to be ordered by postcode. How do I do this?
Using ORDER BY Postcode returns
SK1
SK11
SK13
SK2
How can I return
SK1
SK2
SK11
SK13
EDIT
I should really have added more to the question, I am working with postcodes for the whole of the UK, not just ones starting with SK. So some of these postcodes will start with only 1 letter, some with 2. Also, the second part of the postcode is in the column.
Assuming MSSQL, and that your Postcode field follows a consistent pattern of Char(2) + Number, then you could add a computed query column:
postcode_num = convert(int,substring(postcode,3,len(postcode)))
And then use it instead of Postcode for sorting:
order by postcode_num
Results as desired:
Create 2 columns:
1. a VARCHAR for the first part;
2. a TINYINT for the last (numeric) part.
ORDER BY postcode_prefix, postcode_suffix
Source: https://www.sitepoint.com/community/t/order-by-postcode/50042/9
The problem you are facing is that the column you are trying to ORDER BY is of type text and not numeric, therefore SQL will perform the ordering you're seeing. Instead, if you want SQL to order it as if it was a number then you would need to substring the "SK" part of the column, cast the number characters to numeric type and then order by that.
This is what #LONG replied to you in the first comment.
The way I would approach it is to create a couple of generic functions that will strip the alpha or numeric portions from the string before you sort.
In my example the functions are in the fn schema so change this as you require.
ORDER BY fn.StripToAlpha(PostCode), fn.StripToNumeric(PostCode)
There are plenty of examples of these types of functions around, probably more efficient than the ones I wrote but below is the code to produce the ones I use.
CREATE FUNCTION [fn].[StripToAlpha]
(
#inputString nvarchar(4000)
)
RETURNS varchar(4000)
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 65 AND 90) OR (#ascii BETWEEN 97 AND 122)
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
RETURN #strReturnVal
END
and
CREATE FUNCTION [fn].[StripToNumeric]
(
#inputString nvarchar(4000)
)
RETURNS Float
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #ReturnVal Float
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
IF #inputString IS NULL
BEGIN
Return NULL
END
-- swap out comma for decimal
SET #inputString = REPLACE(#inputString, ',', '.')
IF #Len = 0 OR LEN(LTRIM(RTRIM(#inputString))) = 0
BEGIN
SET #ReturnVal=0
END
ELSE
BEGIN
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 48 AND 57) OR (#ascii IN (46,37))
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
if RIGHT(#strReturnVal,1)='%'
BEGIN
SET #strReturnVal = LEFT(#strReturnVal,len(#strReturnVal)-1)
SET #strReturnVal = CAST((CAST(#strReturnVal AS FLOAT)/100) AS nvarchar(4000))
END
SET #ReturnVal = ISNULL(#strReturnVal,0)
END
RETURN #ReturnVal
END
Notes
This will not affect your current use but the StripToNumeric checks is a percentage sign is present and converts to a decimal so it you pass it 25% it will return 0.25.
This will not work if you use full postcodes such as SK1 1AB as it would sort by SKAB and then 11
It will work on postcodes with shorter prefixes such M34 (That's Denton if I remember correctly ! :) )
You didn't specify database you use; this is an Oracle example. Hopefully, you'll be able to "convert" it to something else.
The idea is: using regular expressions (which seem to be quite handy in such cases), split postcode to two parts: letters and numbers. As REGEXP_SUBSTR returns a string, I applied the TO_NUMBER function to a "numeric" part of the postcode in order to properly sort it.
SQL> with test (postcode) as
2 (select 'sk1' from dual union
3 select 'sk11' from dual union
4 select 'sk13' from dual union
5 select 'sk2' from dual
6 )
7 select postcode
8 from test
9 order by regexp_substr(postcode, '^[[:alpha:]]+'), --> letters
10 to_number(regexp_substr(postcode, '[[:digit:]]+$')); --> numbers
POST
----
sk1
sk2
sk11
sk13
SQL>

Don't understand rounding behavior in sql server when using division operator

Could anybody explain what happens in the following sql code?
declare #dividend numeric(38,22)
declare #divisor numeric(38,22)
declare #otherDivisor int
set #dividend = 1
set #divisor = 3
set #otherDivisor = 3
select cast(#dividend / #divisor as numeric(38,22)), #dividend / #otherDivisor
The result returned is
0.3333330000000000000000 0.3333333333333333333333
I would expect the same result for both calculations.
decimal(38,22) / decimal(38,22) ends up with decimal(x, 6) following these rules
So you have 0.33333 before you cast back to decimal(38,22)
#otherDivisor is cast to (38, 0) and stays as decimal(x,22)
See my worked example
Try this:
select cast(#dividend as numeric(38,22)) / #divisor, #dividend / #otherDivisor
You are casting after doing the division.
Actually I take that answer back. It looks as though SQL is coercing the result to whichever has the higher precedence, dividend or divisor.
select 1.00000000000000000000/3.0
select 1.0/3.0

Splitting decimal in sql

I am getting result as decimal in stored procedure. If I am getting result as 123.45,
I want to split it into 123 and 45. Can anybody help?
use SQL function FLOOR() for getting integer part
and subtract that from the original for the decimal part
You can also make use of ROUND instead of FLOOR.
See section C. Using ROUND to truncate for trucate, and then subtract that from the original.
Be aware that using FLOOR on negative numbers might not give you the required result.
Have a look at this example
DECLARE #Dec DECIMAL(12,8)
SET #Dec = -123.45
SELECT FLOOR(#DEc)
select round(#Dec, 0, 1)
try this;
DECLARE #result DECIMAL(8,2) = 123.45
SELECT CAST(round(#result,0) AS FLOAT)
SELECT REPLACE(#result % 1 ,'0.','')
OR
DECLARE #result decimal(8,2) = 123.45
select PARSENAME(#result, 2) AS LeftSideValue, PARSENAME(#result, 1) AS RightSideValue

SQL Server, division returns zero

Here is the code I'm using in the example:
PRINT #set1
PRINT #set2
SET #weight= #set1 / #set2;
PRINT #weight
Here is the result:
47
638
0
I would like to know why it's returning 0 instead of 0,073667712
Either declare set1 and set2 as floats instead of integers or cast them to floats as part of the calculation:
SET #weight= CAST(#set1 AS float) / CAST(#set2 AS float);
When you use only integers in a division, you will get integer division. When you use (at least one) double or float, you will get floating point division (and the answer you want to get).
So you can
declare one or both of the variables as float/double
cast one or both of the variables to float/double.
Do not just cast the result of the integer division to double: the division was already performed as integer division, so the numbers behind the decimal are already lost.
Simply mutiply the bottom of the division by 1.0 (or as many decimal places as you want)
PRINT #set1
PRINT #set2
SET #weight= #set1 / #set2 *1.00000;
PRINT #weight
Because it's an integer. You need to declare them as floating point numbers or decimals, or cast to such in the calculation.
if you declare it as float or any decimal format it will display
0
only
E.g :
declare #weight float;
SET #weight= 47 / 638; PRINT #weight
Output : 0
If you want the output as
0.073667712
E.g
declare #weight float;
SET #weight= 47.000000000 / 638.000000000; PRINT #weight
In SQL Server direct division of two integer returns integer even if the result should be the float. There is an example below to get it across:
--1--
declare #weird_number_float float
set #weird_number_float=22/7
select #weird_number_float
--2--
declare #weird_number_decimal decimal(18,10)
set #weird_number_decimal=22/7
select #weird_number_decimal
--3--
declare #weird_number_numeric numeric
set #weird_number_numeric=22/7
select #weird_number_numeric
--Right way
declare #weird_number float
set #weird_number=cast(22 as float)/cast(7 as float)
select #weird_number
Just last block will return the 3,14285714285714. In spite of the second block defined with right precision the result will be 3.00000.