Minimize the length of number to fixed length - sql

I need to minimize the length of number to fixed value 3 with decimal point 1
Ex:
After calculation value of #a=45689.45.
Now I need to get value of #a =45.6
If the value is less then 100 i.e if it is 89.63 then I don't need to change it.
At last value of #a should be decimal of (3,1)

How about:
case when #a >= 100 then
round(#a / power(10, floor(log10(#a)) - 1), 1, 1)
else #a
end

Try this
DECLARE #a DECIMAL(7, 2)=45689.45
SELECT CASE
WHEN len(CONVERT(INT, #a)) > 3 THEN LEFT(#a / CONVERT(INT, '1' + replicate(0, len(CONVERT(INT, #a))-2)), 4)
ELSE #a
END

I haven't tested this to death but without using string manipulation this might do the job;
declare #i decimal(18, 6); set #i = 2000
select cast(round(#i / case when #i >= 100 then (power(10, floor(log10(#i)) - 1)) else 1.0 end, 1) as decimal(3,1)
Fingers crossed :)
Rhys

Related

What T-SQL statement can add leading zeros to a column of numbers? [duplicate]

I have the following table A:
id
----
1
2
12
123
1234
I need to left-pad the id values with zero's:
id
----
0001
0002
0012
0123
1234
How can I achieve this?
I believe this may be what your looking for:
SELECT padded_id = REPLACE(STR(id, 4), SPACE(1), '0')
FROM tableA
or
SELECT REPLACE(STR(id, 4), SPACE(1), '0') AS [padded_id]
FROM tableA
I haven't tested the syntax on the 2nd example. I'm not sure if that works 100% - it may require some tweaking - but it conveys the general idea of how to obtain your desired output.
EDIT
To address concerns listed in the comments...
#pkr298 - Yes STR does only work on numbers... The OP's field is an ID... hence number only.
#Desolator - Of course that won't work... the First parameter is 6 characters long. You can do something like:
SELECT REPLACE(STR(id,
(SELECT LEN(MAX(id)) + 4 FROM tableA)), SPACE(1), '0') AS [padded_id] FROM tableA
this should theoretically move the goal posts... as the number gets bigger it should ALWAYS work.... regardless if its 1 or 123456789...
So if your max value is 123456... you would see 0000123456 and if your min value is 1 you would see 0000000001
SQL Server now supports the FORMAT function starting from version 2012, so:
SELECT FORMAT(id, '0000') FROM TableA
will do the trick.
If your id or column is in a varchar and represents a number you convert first:
SELECT FORMAT(CONVERT(INT,id), '0000') FROM TableA
Old post, but maybe this helps someone out:
To complete until it ends up with 4 non-blank characters:
SELECT RIGHT ('0000'+COLUMNNAME, 4) FROM TABLENAME;
To complete until 10:
SELECT RIGHT ('0000000000'+COLUMNNAME, 10) FROM TABLENAME;
In case the column is numeric, convert it to varchar first with such code:
Select RIGHT('0000'+Convert(nvarchar(20), COLUMNNAME), 4)
From TABLENAME
And to complete until 10 with a numeric field:
SELECT RIGHT ('0000000000'+Convert(nvarchar(20), COLUMNNAME), 10) FROM TABLENAME;
declare #T table(id int)
insert into #T values
(1),
(2),
(12),
(123),
(1234)
select right('0000'+convert(varchar(4), id), 4)
from #T
Result
----
0001
0002
0012
0123
1234
Try this:
SELECT RIGHT(REPLICATE('0',4)+CAST(Id AS VARCHAR(4)),4) FROM [Table A]
-- Please look into these.
select FORMAT(1, 'd4');
select FORMAT(2, 'd4');
select FORMAT(12, 'd4');
select FORMAT(123, 'd4');
select FORMAT(1234, 'd4');
-- I hope these would help you
This works for strings, integers and numeric:
SELECT CONCAT(REPLICATE('0', 4 - LEN(id)), id)
Where 4 is desired length. Works for numbers with more than 4 digits, returns empty string on NULL value.
If someone is still interested, I found this article on DATABASE.GUIDE:
Left Padding in SQL Server – 3 LPAD() Equivalents
In short, there are 3 methods mentioned in that article.
Let's say your id=12 and you need it to display as 0012.
Method 1 – Use the RIGHT() Function
The first method uses the RIGHT() function to return only the rightmost part of the string, after adding some leading zeros.
SELECT RIGHT('00' + '12', 4);
Result:
0012
Method 2 – Use a Combination of RIGHT() and REPLICATE()
This method is almost the same as the previous method, with the only difference being that I simply replace the three zeros with the REPLICATE() function:
SELECT RIGHT(REPLICATE('0', 2) + '12', 4);
Result:
0012
Method 3 – Use a Combination of REPLACE() and STR()
This method comes from a completely different angle to the previous methods:
SELECT REPLACE(STR('12', 4),' ','0');
Result:
0012
Check out the article, there is more in depth analysis with examples.
This is what I normally use when I need to pad a value.
SET #PaddedValue = REPLICATE('0', #Length - LEN(#OrigValue)) + CAST(#OrigValue as VARCHAR)
I created a function to do this, where you can specify the desired output character length:
CREATE FUNCTION [dbo].[udfLeadingZero]
(
#String VARCHAR(MAX)
, #Len INT
)
RETURNS VARCHAR(MAX)
BEGIN
SET #String = RIGHT(REPLICATE('0',#Len)+#String,#Len)
RETURN #String
END
GO
Example results
I needed this in a function on SQL server and adjusted Patrick's answer a bit.
declare #dossierId int = 123
declare #padded_id varchar(7)
set #padded_id = REPLACE(
SPACE(7 - LEN(#dossierId)) + convert(varchar(7), #dossierId),
SPACE(1),
'0')
SELECT #dossierId as '#dossierId'
,SPACE(LEN(#dossierId)) + convert(varchar(7)
,#dossierId) as withSpaces
,#padded_id as '#padded_id'
Create Function :
Create FUNCTION [dbo].[PadLeft]
(
#Text NVARCHAR(MAX) ,
#Replace NVARCHAR(MAX) ,
#Len INT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #var NVARCHAR(MAX)
SELECT #var = ISNULL(LTRIM(RTRIM(#Text)) , '')
RETURN RIGHT(REPLICATE(#Replace,#Len)+ #var, #Len)
END
Example:
Select dbo.PadLeft('123456','0',8)
I created a function:
CREATE FUNCTION [dbo].[fnPadLeft](#int int, #Length tinyint)
RETURNS varchar(255)
AS
BEGIN
DECLARE #strInt varchar(255)
SET #strInt = CAST(#int as varchar(255))
RETURN (REPLICATE('0', (#Length - LEN(#strInt))) + #strInt);
END;
Use: select dbo.fnPadLeft(123, 10)
Returns: 0000000123
Something fairly ODBC compliant if needed might be the following:
select ifnull(repeat('0', 5 - (floor(log10(FIELD_NAME)) + 1)), '')
+ cast (FIELD as varchar(10))
from TABLE_NAME
This bases on the fact that the amount of digits for a base-10 number can be found by the integral component of its log. From this we can subtract it from the desired padding width. Repeat will return null for values under 1 so we need ifnull.
My solution is not efficient but helped me in situation where the values (bank cheque numbers and wire transfer ref no.) were stored as varchar where some entries had alpha numeric values with them and I had to pad if length is smaller than 6 chars.
Thought to share if someone comes across same situation
declare #minlen int = 6
declare #str varchar(20)
set #str = '123'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: 000123
set #str = '1234'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: 001234
set #str = '123456'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: 123456
set #str = '123456789'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: 123456789
set #str = '123456789'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: 123456789
set #str = 'NEFT 123456789'
select case when len(#str) < #minlen then REPLICATE('0',#minlen-len(#str))+#str else #str end
--Ans: NEFT 123456789
A simple example would be
DECLARE #number INTEGER
DECLARE #length INTEGER
DECLARE #char NVARCHAR(10)
SET #number = 1
SET #length = 5
SET #char = '0'
SELECT FORMAT(#number, replicate(#char, #length))
More efficient way is :
Select id, LEN(id)
From TableA
Order by 2,1
The result :
id
----
1
2
12
123
1234

parsing an integer field in sql

I want to split every digit in an integer field in an sql table . example of that would be:
financialNb = 7869
i need the 7 as first digit, 8 as second, 6 as third and 9 as fourth. this is needed because I want every digit to be used as 1 data field in a crystal report?
First you have to convert or cast numbers into text before you can manipulate them in this way. The functions you are looking for are CAST() and SUBSTRING(). To get the numbers to start from the right, you can use REVERSE().
Try this example:
SELECT 7869 AS field1
INTO #tmp
SELECT SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),8,1) AS [Column8]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),7,1) AS [Column7]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),6,1) AS [Column6]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),5,1) AS [Column5]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),4,1) AS [Column4]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),3,1) AS [Column3]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),2,1) AS [Column2]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),1,1) AS [Column1]
FROM #tmp
DROP TABLE #tmp
Presumably your question is about parsing 7869. Using the substring function for this:
select substring(cast(<col> as char(4)), 1, 1) as FirstChar,
substring(cast(<col> as char(4)), 2, 1) as SecondChar,
substring(cast(<col> as char(4)), 3, 1) as ThirdChar,
substring(cast(<col> as char(4)), 4, 1) as FourthChar
from YourTable
I might be interpreting the ordering of the digits incorrectly, and this assumes the strings are always 4 digits and that you want characters. An alternative way is just to look at this as numbers:
select <col%10 as FirstNum,
(col/10) %10 as SecondNum,
(col/100)%10 as ThirdNum,
(col/1000)%10 as FourthNum
You can use the modulo operator (%) to get the digits of an integer, without using the slower string manipulation functions, like this
select
value % 100000000 / 10000000,
value % 10000000 / 1000000,
value % 1000000 / 100000,
value % 100000 / 10000,
value % 10000 / 1000,
value % 1000 / 100,
value % 100 / 10,
value % 10
from testData
Here is a SQL Fiddle to play with.
If the field is always 4 numbers long, you can get away with:
select
value / 1000 as Thousands,
value % 1000 / 100 as Hundreds,
value % 100 / 10 as Tens,
value % 10 as Units
from testData
If, however, you need to use it on arbitrary numbers, you could create a user-defined table valued function, that will return the digits in a table, like this:
create function dbo.getDigits(#Input int)
returns #Digits table
(
Digit int,
Position int
)
as begin
declare #pos int
declare #digit int
set #pos = 0
if #input = 0
begin
-- zero is just a single zero digit at position 1
insert into #digits values (0,1)
return
end
while #input<>0 begin
set #pos=#pos+1
set #digit = #input % 10
set #input = #input / 10
insert into #digits values (#digit, #pos)
end
return
end
and use it like this:
SELECT td.ID, td.Value, d.Digit, d.Position
FROM testData td
CROSS APPLY dbo.getDigits(td.Value) AS d
order by td.ID, d.Position Desc
(Here's another SQL Fiddle, based on the previous one)

Generate random int value from 3 to 6

Is it possible in Microsoft SQL Server generate random int value from Min to Max (3-9 example, 15-99 e.t.c)
I know, I can generate from 0 to Max, but how to increase Min border?
This query generate random value from 1 to 6. Need to change it from 3 to 6.
SELECT table_name, 1.0 + floor(6 * RAND(convert(varbinary, newid()))) magic_number
FROM information_schema.tables
Added 5 sec later:
SELECT table_name, 3.0 + floor(4 * RAND(convert(varbinary, newid()))) magic_number
FROM information_schema.tables
A helpful editor added the 'Select' before each statement but the point of this item is that it can generate unique keys for each row in a return, not just one item (For that I would us the Rand() function).
For example:
Select top 100 Rand(),* from tblExample
Would return the same random value for all 100 rows.
While:
Select top 100 ABS(CHECKSUM(NEWID()) % 10),* from tblexample
Would return a different random value between 0 and 9 on each row in the return.
So while the select makes it easier to copy and paste, you can copy the logic into a select statement if that is what is required.
This generates a random number between 0-9
SELECT ABS(CHECKSUM(NEWID()) % 10)
1 through 6
SELECT ABS(CHECKSUM(NEWID()) % 6) + 1
3 through 6
SELECT ABS(CHECKSUM(NEWID()) % 4) + 3
Dynamic (Based on Eilert Hjelmeseths Comment - thanks to jiraiya for providing the visual presentation)
SELECT ABS(CHECKSUM(NEWID()) % (#max - #min + 1)) + #min
Updated based on comments:
NEWID generates random string (for each row in return)
CHECKSUM takes value of string and creates number
modulus (%) divides by that number and returns the remainder (meaning max value is one less than the number you use)
ABS changes negative results to positive
then add one to the result to eliminate 0 results (to simulate a dice roll)
I see you have added an answer to your question in SQL Server 2008 you can also do
SELECT 3 + CRYPT_GEN_RANDOM(1) % 4 /*Random number between 3 and 6*/
FROM ...
A couple of disadvantages of this method are
This is slower than the NEWID() method
Even though it is evaluated once per row the query optimiser does not realise this which can lead to odd results.
but just thought I'd add it as another option.
You can do this:
DECLARE #maxval TINYINT, #minval TINYINT
select #maxval=24,#minval=5
SELECT CAST(((#maxval + 1) - #minval) *
RAND(CHECKSUM(NEWID())) + #minval AS TINYINT)
And that was taken directly from this link, I don't really know how to give proper credit for this answer.
Here is the simple and single line of code
For this use the SQL Inbuild RAND() function.
Here is the formula to generate random number between two number (RETURN INT Range)
Here a is your First Number (Min) and b is the Second Number (Max) in Range
SELECT FLOOR(RAND()*(b-a)+a)
Note: You can use CAST or CONVERT function as well to get INT range number.
( CAST(RAND()*(25-10)+10 AS INT) )
Example:
SELECT FLOOR(RAND()*(25-10)+10);
Here is the formula to generate random number between two number (RETURN DECIMAL Range)
SELECT RAND()*(b-a)+a;
Example:
SELECT RAND()*(25-10)+10;
More details check this: https://www.techonthenet.com/sql_server/functions/rand.php
Simply:
DECLARE #MIN INT=3; --We define minimum value, it can be generated.
DECLARE #MAX INT=6; --We define maximum value, it can be generated.
SELECT #MIN+FLOOR((#MAX-#MIN+1)*RAND(CONVERT(VARBINARY,NEWID()))); --And then this T-SQL snippet generates an integer between minimum and maximum integer values.
You can change and edit this code for your needs.
Nice and simple, from Pinal Dave's site:
http://blog.sqlauthority.com/2007/04/29/sql-server-random-number-generator-script-sql-query/
DECLARE #Random INT;
DECLARE #Upper INT;
DECLARE #Lower INT
SET #Lower = 3 ---- The lowest random number
SET #Upper = 7 ---- One more than the highest random number
SELECT #Random = ROUND(((#Upper - #Lower -1) * RAND() + #Lower), 0)
SELECT #Random
(I did make a slight change to the #Upper- to include the upper number, added 1.)
In general:
select rand()*(#upper-#lower)+#lower;
For your question:
select rand()*(6-3)+3;
<=>
select rand()*3+3;
SELECT ROUND((6 - 3 * RAND()), 0)
Lamak's answer as a function:
-- Create RANDBETWEEN function
-- Usage: SELECT dbo.RANDBETWEEN(0,9,RAND(CHECKSUM(NEWID())))
CREATE FUNCTION dbo.RANDBETWEEN(#minval TINYINT, #maxval TINYINT, #random NUMERIC(18,10))
RETURNS TINYINT
AS
BEGIN
RETURN (SELECT CAST(((#maxval + 1) - #minval) * #random + #minval AS TINYINT))
END
GO
DECLARE #min INT = 3;
DECLARE #max INT = 6;
SELECT #min + ROUND(RAND() * (#max - #min), 0);
Step by step
DECLARE #min INT = 3;
DECLARE #max INT = 6;
DECLARE #rand DECIMAL(19,4) = RAND();
DECLARE #difference INT = #max - #min;
DECLARE #chunk INT = ROUND(#rand * #difference, 0);
DECLARE #result INT = #min + #chunk;
SELECT #result;
Note that a user-defined function thus not allow the use of RAND(). A workaround for this (source: http://blog.sqlauthority.com/2012/11/20/sql-server-using-rand-in-user-defined-functions-udf/) is to create a view first.
CREATE VIEW [dbo].[vw_RandomSeed]
AS
SELECT RAND() AS seed
and then create the random function
CREATE FUNCTION udf_RandomNumberBetween
(
#min INT,
#max INT
)
RETURNS INT
AS
BEGIN
RETURN #min + ROUND((SELECT TOP 1 seed FROM vw_RandomSeed) * (#max - #min), 0);
END

SQL2000 safely cast a VARCHAR(256) to INT

I'm having some problem safely casting a varchar to int on SQL2000.
Part 1 of my problem was that IsNumeric returns false positives if your looking for integers only. I'm aware though why IsNumeric does this though (floats, money etcetera are numeric too) so i looked for an IsInteger function on google.
I found the following User Defined Function (UDF):
CREATE FUNCTION dbo.IsInteger
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
RETURN CASE
WHEN PATINDEX('%[^0-9-]%', #num) = 0
AND CHARINDEX('-', #num) <= 1
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
THEN
1
ELSE
0
END
END
this seems to do a good job checking for integers:
declare #num varchar(256);
declare #num2 varchar(256);
set #num = '22312311';
set #num2 = '22312311.0';
SELECT #num AS [character],
dbo.IsInteger(#num) AS [isInteger],
CASE dbo.IsInteger(#num)WHEN 1 THEN convert(int, #num) ELSE NULL END AS [integer]
UNION
SELECT #num2 AS [character],
dbo.IsInteger(#num2) AS [isInteger],
CASE dbo.IsInteger(#num2)WHEN 1 THEN convert(int, #num2) ELSE NULL END AS [integer];
However it won't validate if the integer is within range (-2^31 <=> 2^31 - 1)
declare #num varchar(256);
set #num = '2147483648';
SELECT #num AS [character],
dbo.IsInteger(#num) AS [isInteger],
CASE dbo.IsInteger(#num)WHEN 1 THEN convert(int, #num) ELSE NULL END AS [integer];
Which throws
Server: Msg 248, Level 16, State 1, Line 3
The conversion of the nvarchar value '2147483648' overflowed an int column. Maximum integer value exceeded.
SQL2000 doesn't have TRY/CATCH (answer presumes ISNUMERIC() returns no false positives) and casting errors cause the entire batch to fail even within UDF's according to this website:
When an error occurs in a UDF,
execution of the function is aborted
immediately and so is the query, and
unless the error is one that aborts
the batch, execution continues on the
next statement – but ##error is 0!
and even if they didn't would still obscure ##error. I also can't cast to bigint since it might still crash (albeit not as often) and this query is part of a UNION which is output to XML which is further validated and transformed with XSLT by a VB6 COM DLL and displayed on a website coded back in 2001 so I really (no really) do not want to change the query output!.
So this leaves me stuck on this seemingly easy task:
if varchar is castable to int cast to int otherwise give me NULL
Any pointers / solutions would be much apreciated but please note that I can't, under no circumstance, change the source column's datatype nor change the validation when data is entered.
Edit:
You can not have numbers over decimal(38,0) in SQL Server (+/- 10^38 -1) so can not trap them or convert them. Which means 37 characters may length and a CAST to decimal(38,0)
SELECT
CASE
WHEN CAST(MyColumn AS decimal(38,0) BETWEEN -2147483648 AND 2147483647 THEN CAST(MyColumn AS int)
ELSE NULL
END
FROM
MyTable
WHERE
ISNUMERIC(MyColumn + '.0e0') = 1 AND LEN(MyColumn) <= 37
Respect to this article for the .0e0 trick
EDIT OP
This question lead me to the folowing updated IsInteger function.
CREATE FUNCTION dbo.IsInteger
(
#num VARCHAR(256)
)
RETURNS BIT
BEGIN
RETURN CASE
WHEN ISNUMERIC(#num + '.0e0') = 1 AND convert(decimal(38,0), #num) BETWEEN -2147483648 AND 2147483647 THEN 1
ELSE 0
END
END
You could just add a couple more checks into the function:
CREATE FUNCTION [dbo].[IsInteger]
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
DECLARE #IsInt BIT
SELECT #IsInt = CASE
WHEN PATINDEX('%[^0-9-]%', #num) = 0
AND CHARINDEX('-', #num) <= 1
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
THEN
1
ELSE
0
END
IF #IsInt = 1
BEGIN
IF LEN(#num) <= 11
BEGIN
DECLARE #test bigint
SELECT #test = convert(bigint, #num)
IF #test <= 2147483647 AND #test >= -2147483648
BEGIN
set #IsInt = 1
END
ELSE
BEGIN
set #IsInt = 0
END
END
ELSE
BEGIN
set #IsInt = 0
END
END
RETURN #IsInt
END
I've not had a chance to test but I think it should work - I've left it as verbose as possible

USPS ACS Keyline Check Digit

I have implemented the "MOD 10" check digit algorithm using SQL, for the US Postal Service Address Change Service Keyline according to the method in their document, but it seems I'm getting the wrong numbers! Our input strings have only numbers in them, making the calculation a little easier. When I compare my results with the results from their testing application, I get different numbers. I don't understand what is going on? Does anyone see anything wrong with my algorithm? It's got to be something obvious...
The documentation for the method can be found on page 12-13 of this document:
http://www.usps.com/cpim/ftp/pubs/pub8a.pdf
The sample application can be found at:
http://ribbs.usps.gov/acs/documents/tech_guides/KEYLINE.EXE
PLEASE NOTE: I fixed the code below, based on the help from forum users. This is so that future readers will be able to use the code in its entirety.
ALTER function [dbo].[udf_create_acs] (#MasterCustomerId varchar(26))
returns varchar(30)
as
begin
--this implements the "mod 10" check digit calculation
--for the US Postal Service ACS function, from "Publication 8A"
--found at "http://www.usps.com/cpim/ftp/pubs/pub8a.pdf"
declare #result varchar(30)
declare #current_char int
declare #char_positions_odd varchar(10)
declare #char_positions_even varchar(10)
declare #total_value int
declare #check_digit varchar(1)
--These strings represent the pre-calculated values of each character
--Example: '7' in an odd position in the input becomes 14, which is 1+4=5
-- so the '7' is in position 5 in the string - zero-indexed
set #char_positions_odd = '0516273849'
set #char_positions_even = '0123456789'
set #total_value = 0
set #current_char = 1
--stepping through the string one character at a time
while (#current_char <= len(#MasterCustomerId)) begin
--this is the calculation for the character's weighted value
if (#current_char % 2 = 0) begin
--it is an even position, so just add the digit's value
set #total_value = #total_value + convert(int, substring(#MasterCustomerId, #current_char, 1))
end else begin
--it is an odd position, so add the pre-calculated value for the digit
set #total_value = #total_value + (charindex(substring(#MasterCustomerId, #current_char, 1), #char_positions_odd) - 1)
end
set #current_char = #current_char + 1
end
--find the check digit (character) using the formula in the USPS document
set #check_digit = convert(varchar,(10 - (#total_value % 10)) % 10)
set #result = '#' + #MasterCustomerId + ' ' + #check_digit + '#'
return #result
end
I'm not sure why you're messing with the whole string representations when you're working in a set-based language.
I'd probably do it like below. I ran four tests through and they were all successful. You can expand this easily to handle characters as well and you could even make the table permanent if you really wanted to do that.
CREATE FUNCTION dbo.Get_Mod10
(
#original_string VARCHAR(26)
)
RETURNS VARCHAR(30)
AS
BEGIN
DECLARE
#value_mapping TABLE (original_char CHAR(1) NOT NULL, odd_value TINYINT NOT NULL, even_value TINYINT NOT NULL)
INSERT INTO #value_mapping
(
original_char,
odd_value,
even_value
)
SELECT '0', 0, 0 UNION
SELECT '1', 2, 1 UNION
SELECT '2', 4, 2 UNION
SELECT '3', 6, 3 UNION
SELECT '4', 8, 4 UNION
SELECT '5', 1, 5 UNION
SELECT '6', 3, 6 UNION
SELECT '7', 5, 7 UNION
SELECT '8', 7, 8 UNION
SELECT '9', 9, 9
DECLARE
#i INT,
#clean_string VARCHAR(26),
#len_string TINYINT,
#sum SMALLINT
SET #clean_string = REPLACE(#original_string, ' ', '')
SET #len_string = LEN(#clean_string)
SET #i = 1
SET #sum = 0
WHILE (#i <= #len_string)
BEGIN
SELECT
#sum = #sum + CASE WHEN #i % 2 = 0 THEN even_value ELSE odd_value END
FROM
#value_mapping
WHERE
original_char = SUBSTRING(#clean_string, #i, 1)
SET #i = #i + 1
END
RETURN (10 - (#sum % 10)) % 10
END
GO
set #check_digit = convert(varchar, (10 - (#total_value % 10)) % 10)
Why do we have an additional mod:
convert(varchar, 10 % <<-- ?
The document says that only the last digit needs to be subtracted from 10. Did I miss anything?