I need to display some data in an SSRS 2008r2 report and the colors have to match a Windows VB app that saves it's colors as integers (e.g.16744703 is a pinkish color). I believe this is ARGB format. I'm not concerned about the alpha value, as the application does not allow the user to modify it.
I'm stuck on the SQL to convert ARGB to something compatible in SSRS. I need to do the translation in SQL as there are other factors that may override an objects color.
I can work with 3 ints for rgb or a hex value
Anyone got any idea how tot do this?
Regards
mark
Figured it out. Here's a function that returs either RGB() or Hex
-- Description: Converts ARGB to RGB(RR,GG,BB)
-- e.g. 16744703 returns RGB(255,128,255) or #FF80FF
CREATE FUNCTION [dbo].[ARGB2RGB]
(
#ARGB AS BIGINT
,#ColorType AS VARCHAR(1) -- 'H' = Hex, 'R' = RGB
)
RETURNS VARCHAR(16)
AS
BEGIN
DECLARE #Octet1 TINYINT
DECLARE #Octet2 TINYINT
DECLARE #Octet3 TINYINT
DECLARE #Octet4 TINYINT
DECLARE #RestOfColor BIGINT
SET #Octet1 = #ARGB / 16777216
SET #RestOfColor = #ARGB - ( #Octet1 * CAST(16777216 AS BIGINT) )
SET #Octet2 = #RestOfColor / 65536
SET #RestOfColor = #RestOfColor - ( #Octet2 * 65536 )
SET #Octet3 = #RestOfColor / 256
SET #Octet4 = #RestOfColor - ( #Octet3 * 256 )
RETURN
CASE #ColorType
WHEN 'R'
THEN 'RGB(' + CONVERT(VARCHAR, #Octet4) + ','
+ CONVERT(VARCHAR, #Octet3) + ',' + CONVERT(VARCHAR, #Octet2)
+ ')'
WHEN 'H'
THEN '#' + RIGHT(sys.fn_varbintohexstr(#Octet4), 2)
+ RIGHT(sys.fn_varbintohexstr(#Octet3), 2)
+ RIGHT(sys.fn_varbintohexstr(#Octet2), 2)
END
END
Hope someone else finds it useful
Regards
Mark
create FUNCTION [dbo].[ConvertRGB]
(
#ARGB AS float
)
RETURNS #ReturnValue TABLE ( R TINYINT,B TINYINT, G TINYINT )
as
BEGIN
DECLARE #testvarbinary binary(4)
DECLARE #strRBG nvarchar(MAX)
set #testvarbinary = CONVERT(binary(4),#ARGB)
set #strRBG=( SELECT substring(sys.fn_varbintohexstr(#testvarbinary),5,6))
DECLARE #temp AS TABLE (hex char(6))
INSERT INTO #temp
VALUES (#strRBG)
DECLARE #strHex AS varchar(16)
SET #strHex = '0123456789abcdef' -- Assuming case-insensitive collation!
INSERT INTO #ReturnValue
( R,G,B )
SELECT 16 * (CHARINDEX(SUBSTRING(hex, 1, 1), #strHex) - 1) + (CHARINDEX(SUBSTRING(hex, 2, 1), #strHex) - 1)
,16 * (CHARINDEX(SUBSTRING(hex, 3, 1), #strHex) - 1) + (CHARINDEX(SUBSTRING(hex, 4, 1), #strHex) - 1)
,16 * (CHARINDEX(SUBSTRING(hex, 5, 1), #strHex) - 1) + (CHARINDEX(SUBSTRING(hex, 6, 1), #strHex) - 1)
FROM #temp
RETURN
END;
GO
--select * from [ConvertRGB](10592513)
I was looking for doing something like this and came up with this post. It was of great help but I found it more appealing doing it inline as follow. Notice that the result can be used inside the SSRS Color Expression with no additional conversions
select CustomerID
,SSRSColor = '#' + SUBSTRING(S.TXTHEXColor, 5, 2)
+ SUBSTRING(S.TXTHEXColor, 3, 2)
+ SUBSTRING(S.TXTHEXColor, 1, 2)
from
(
Select CustomerID
,[TXTHEXColor] = right(sys.fn_varbintohexstr(CONVERT(varbinary, T.Color)), 6)
From SomeTable T
) S
If a function is still needed then following is a much shorter way but bear in mind that with big record sets functions can slow down the process a lot.
CREATE FUNCTION [dbo].[SSRSColor] (
#ARGB AS INT
)
RETURNS VARCHAR(7)
AS
BEGIN
DECLARE #TXTHEXColor varchar(100)
Select #TXTHEXColor = right(sys.fn_varbintohexstr(CONVERT(varbinary, #ARGB)), 6)
return '#' + SUBSTRING(#TXTHEXColor, 5, 2) + SUBSTRING(#TXTHEXColor, 3, 2) + SUBSTRING(#TXTHEXColor, 1, 2)
END
Related
I am using below function to generate 5 letters random key but it is the combination of all letters and numbers.
But as per requirements I need to generate a random unique key that does not contain the characters 'o' and 'I', and also does not contain the numbers '0' and '1' .
DECLARE #automateKey VARCHAR(15)
DECLARE #Length INT = 6
DECLARE #Count INT = 2
SET #Length = #Length + 1
SET #Count = #Count + 1
SELECT #automateKey =
(SELECT CAST((ABS(Checksum(NewId())) % 10) AS VARCHAR(1)) +
CHAR(ascii('A')+(Abs(Checksum(NewId()))%25)) +
LEFT(newid(),#count) Random_Number)
SELECT (#automateKey)
I am not sure how I can escape the those particular characters and numbers from random key generation
Could anyone please help with this query? Many thanks in advance.
Use RAND() is better than NEWID(), as it is not random in nature.
DECLARE
#Chars varchar(100) = 'ABCDEFGHJKLMNPQRSTUVWXYZ',
#CharsAndNumbers varchar(100) = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'
SELECT
CONCAT
(
-- Numbers 2-9
FLOOR(RAND() * 8 + 2),
-- Any UPPER CASE character but not I,O
SUBSTRING(#Chars, CONVERT(int, RAND() * LEN(#Chars) + 1), 1),
-- Numbers 2-9 and Any UPPER CASE charcter but not I,O
SUBSTRING(#CharsAndNumbers, CONVERT(int, (RAND() * LEN(#CharsAndNumbers)) + 1), 1),
SUBSTRING(#CharsAndNumbers, CONVERT(int, (RAND() * LEN(#CharsAndNumbers)) + 1), 1),
SUBSTRING(#CharsAndNumbers, CONVERT(int, (RAND() * LEN(#CharsAndNumbers)) + 1), 1)
)
Testing SQL in StackExchange Data Explorer
I would suggest doing something like this:
declare #chars varchar(255);
set #chars = 'ABCDEFGHIJKLMNPQRSTUVWXYZ23456789';
declare #i int;
set #i = 1;
declare #automateKey = varchar(255);
set #automatekey = '';
while #i <= 5
begin
set #automateKey = #automateKey + substring(#chars, cast(rand() * len(#chars) + 1 as int), 1)
set #i = #i + 1;
end;
This solution has two main components. First, all the valid characters are defined as a string. Second, it uses a while loop to set the values using rand().
rand() behaves in a strange way in SQL Server -- it is evaluated only once for a given query when it appears. Hence, I generally do not want to use it in a SELECT statement.
I should add that the following can replace the WHILE loop:
set #automateKey = (substring(#chars, cast(rand() * len(#chars) + 1 as int), 1) +
substring(#chars, cast(rand() * len(#chars) + 1 as int), 1) +
substring(#chars, cast(rand() * len(#chars) + 1 as int), 1) +
substring(#chars, cast(rand() * len(#chars) + 1 as int), 1) +
substring(#chars, cast(rand() * len(#chars) + 1 as int), 1)
);
Your key seems to have 3 parts:
First character is random number 0-10 which is genereted with:
(Abs(Checksum(NewId()))%10)
Second character is random letter which is generated with:
CHAR(ascii('A')+(Abs(Checksum(NewId()))%25))
Third part is three characters that could be either letter or number
LEFT(newid(),#count)
It's not hard to change the first two parts to eliminate unwanted characters, but would require bit of changing to third part.
Instead you can just leave this as is, and add to the end while loop to search and replace unwanted characters - 0 and 1 with random number, O and L with random letter:
DECLARE #automateKey VARCHAR(15)
DECLARE #Length INT = 6
DECLARE #Count INT = 2
SET #Length = #Length + 1
SET #Count = #Count + 1
--this seems unnecessary , why not just SET #Count = 3 ?
SELECT #automateKey =
(SELECT CAST((ABS(Checksum(NewId())) % 10) AS VARCHAR(1)) +
CHAR(ascii('A')+(Abs(Checksum(NewId()))%25)) +
LEFT(newid(),#count) Random_Number)
WHILE (#automateKey LIKE '%0%' OR #automateKey LIKE '%1%')
BEGIN
SELECT #automateKey = REPLACE(#automateKey, '0', (Abs(Checksum(NewId()))%10))
SELECT #automateKey = REPLACE(#automateKey, '1', (Abs(Checksum(NewId()))%10))
END
WHILE (#automateKey LIKE '%O%' OR #automateKey LIKE '%L%')
BEGIN
SELECT #automateKey = REPLACE(#automateKey, 'O', CHAR(ascii('A')+(Abs(Checksum(NewId()))%25)))
SELECT #automateKey = REPLACE(#automateKey, 'L', CHAR(ascii('A')+(Abs(Checksum(NewId()))%25)))
END
SELECT #automateKey
I am trying to write a function that will output some address information on a CIDR formatted IP (output underneath code):
create function dbo.ConvertIpToInt (#Ip as varchar(15))
returns bigint
as
begin
return (convert(bigint, parsename(#Ip, 1)) +
convert(bigint, parsename(#Ip, 2)) * 256 +
convert(bigint, parsename(#Ip, 3)) * 256 * 256 +
convert(bigint, parsename(#Ip, 4)) * 256 * 256 * 256)
end
go
create function dbo.ConvertIntToIp (#Int bigint)
returns varchar(15)
as
begin
declare
#IpHex varchar(8)
,#IpDotted varchar(15)
select
#IpHex = substring(convert(varchar(30), master.dbo.fn_varbintohexstr(#Int)), 11, 8)
select
#IpDotted = convert(varchar(3), convert(int, (convert(varbinary, substring(#IpHex, 1, 2), 2)))) + '.' +
convert(varchar(3), convert(int, (convert(varbinary, substring(#IpHex, 3, 2), 2)))) + '.' +
convert(varchar(3), convert(int, (convert(varbinary, substring(#IpHex, 5, 2), 2)))) + '.' +
convert(varchar(3), convert(int, (convert(varbinary, substring(#IpHex, 7, 2), 2))))
return #IpDotted
end
go
create function dbo.GetCidrIpRange (#CidrIp varchar(15))
returns #result table
(
CidrIp varchar(15) not null,
Mask int not null,
LowRange varchar(15) not null,
LowIp varchar(15) not null,
HighRange varchar(15) not null,
HighIp varchar(15) not null,
AddressQty bigint not null
)
as
begin
declare #Base bigint = cast(4294967295 as bigint)
declare #Mask int = cast(substring(#CidrIp, patindex('%/%' , #CidrIP) + 1, 2) as int)
declare #Power bigint = Power(2.0, 32.0 - #Mask) - 1
declare #LowRange bigint = dbo.ConvertIpToInt(left(#CidrIp, patindex('%/%' , #CidrIp) - 1)) & (#Base ^ #Power)
declare #HighRange bigint = #LowRange + #Power
insert #result
select
CidrIp = #CidrIp
, Mask = #Mask
, LowRange = #LowRange
, LowIp = dbo.ConvertIntToIp(#LowRange)
, HighRange = #HighRange
, HighIp = dbo.ConvertIntToIp(#HighRange)
, AddressQty = convert(bigint, power(2.0, (32.0 - #Mask)))
return
end
go
select * from dbo.GetCidrIpRange('195.65.254.11/2');
This outputs the following:
CidrIp Mask LowRange LowIp HighRange HighIp AddressQty
--------------------------------------------------------------------------------------
195.65.254.11/2 2 3221225472 192.0.0.0 4294967295 255.255.255.255 1073741824
I have been browsing SO and Google for some hours now, and I am quite convinced that ConvertIpToInt and ConvertIntToIp are correct.
However, I was expecting the following output:
CidrIp Mask LowRange LowIp HighRange HighIp AddressQty
--------------------------------------------------------------------------------------
195.65.254.11/2 2 3275881985 195.65.254.1 3275882238 195.65.254.254 254
Can someone please point me out where the mistake in my code is? I've been staring myself blind and I don't see it (or I am misunderstanding how to do this).
According to both http://www.ipaddressguide.com/cidr and http://jodies.de/ipcalc?host=195.65.254.11&mask1=2&mask2=, your calculations are correct. The only disagreement between those two sites is that the jodies.de/ipcalc page removes the lowest and highest (broadcast) IP addresses from the range.
I tested with both 195.65.254.11/2 and 195.65.254.11/24. In order to get your code working, I needed to change the input parameter specification on dbo.GetCidrIpRang to be VARCHAR(20) (as mentioned by #Damien_The_Unbeliever in a comment on the question).
Two notes regarding performance:
For the ConvertIpToInt and ConvertIntToIp Scalar UDFs you might be better off using the INET_AddressToNumber and INET_NumberToAddress functions, respectively, that are included in the Free version of the SQL# SQLCLR library (which I wrote, but hey, Free :). The reason for this recommendation is that unlike T-SQL UDFs, deterministic SQLCLR UDFs (and these two are) do not prevent parallel plans.
If you don't want to go the SQLCLR route, then you should, at the very least, keep the ConvertIntToIp function as purely mathematical. There is no reason to do all of those conversions and substrings.
CREATE FUNCTION dbo.IPNumberToAddress(#IPNumber BIGINT)
RETURNS VARCHAR(15)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #Oct1 BIGINT,
#Oct2 INT,
#Oct3 INT;
SET #Oct1 = #IPNumber / (256 * 256 * 256);
SET #IPNumber -= (#Oct1 * (256 * 256 * 256));
SET #Oct2 = #IPNumber / (256 * 256);
SET #IPNumber -= (#Oct2 * (256 * 256));
SET #Oct3 = #IPNumber / 256;
SET #IPNumber -= (#Oct3 * 256);
RETURN CONCAT(#Oct1, '.', #Oct2, '.', #Oct3, '.', #IPNumber);
END;
GO
And then:
SELECT dbo.IPNumberToAddress(3275881995);
-- 195.65.254.11
For the GetCidrIpRange TVF, you would be better off converting that to be an Inline TVF. You can accomplish the multi-step calculations via CTEs in the following manner (you will just need to clean it up a little / finish it):
WITH cte1 AS
(
SELECT 2 AS [Mask] -- replace with real formula
), cte2 AS
(
SELECT 999 AS [Base], -- replace with real formula
POWER(2.0, 32.0 - cte1.[Mask]) - 1 AS [Power],
cte1.[Mask]
FROM cte1
), cte3 AS
(
SELECT SQL#.INET_AddressToNumber(left(#CidrIp, PATINDEX('%/%' , #CidrIp) - 1))
& (cte2.[Base] ^ cte2.[Power]) AS [LowRange],
cte2.[Power],
cte2.[Mask]
FROM cte2
)
SELECT #CidrIp AS [CidrIp],
cte3.[Mask],
cte3.[LowRange],
SQL#.INET_NumberToAddress(cte3.[LowRange]) AS [LowIp],
(cte3.[LowRange] + cte3.[Power]) AS [HighRange],
SQL#.INET_NumberToAddress(cte3.[LowRange] + cte3.[Power]) AS [HighIp],
CONVERT(BIGINT, POWER(2.0, (32.0 - cte3.[Mask]))) AS [AddressQty]
FROM cte3 c;
I am migrating sensitive data to a database, and I need to hide details of the text. We would like to keep the volume and length of the text, but change the meaning.
For example:
"James has been well received, and should be helped when ever he finds it hard to speak"
should change to:
"jhdfy dfw aslk dfe kjdfkjd, kjf kjdsf df iotryy erhr lsdj jf ytwe it kjdf tr kjsdd"
Is there a way to update all rows, set the column text to this random type text? Really only want to change charactors (a-z, A-Z), and keep the rest.
One option is to use a bunch of nested replaces . . . but that would probably hit on the maximum number of nested functions.
You could write a painful query using outer apply:
select
from t outer apply
(select replace(t.col, 'a', 'z') as col1) outer apply
(select replace(col1, 'b', 'y') ) outer apply
. . .
However, you might want to write your own function. In other databases, this is called translate() (after the Unix command). If you Google SQL Server translate, I think you'll find examples on the web.
One way is to split the string character by character and replace each row with a random string. And then concatenate them back to get the desired output
DECLARE #str VARCHAR(MAX) = 'James has been well received, and should be helped when ever he finds it hard to speak'
;WITH Cte(orig, random) AS(
SELECT
SUBSTRING(t.a, v.number + 1, 1),
CASE
WHEN SUBSTRING(t.a, v.number + 1, 1) LIKE '[a-z]'
THEN CHAR(ABS(CHECKSUM(NEWID())) % 25 + 97)
ELSE SUBSTRING(t.a, v.number + 1, 1)
END
FROM (SELECT #str) t(a)
CROSS JOIN master..spt_values v
WHERE
v.number < LEN(t.a)
AND v.type = 'P'
)
SELECT
OrignalString = #str,
RandomString = (
SELECT '' + random
FROM Cte FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'
)
TRY IT HERE
OK this is possible using a user defined function (UDF) and a view.
SQL Server does not allow random number generation in a UDF but does allow it in a view. Ref: http://blog.sqlauthority.com/2012/11/20/sql-server-using-rand-in-user-defined-functions-udf/
So here is the solution
CREATE VIEW [dbo].[rndView]
AS
SELECT RAND() rndResult
GO
CREATE FUNCTION [dbo].[RandFn]()
RETURNS float
AS
BEGIN
DECLARE #rndValue float
SELECT #rndValue = rndResult
FROM rndView
RETURN #rndValue
END
GO
CREATE FUNCTION [dbo].[randomstring] ( #stringToParse VARCHAR(MAX))
RETURNS
varchar(max)
AS
BEGIN
/*
A = 65
Z = 90
a = 97
z = 112
declare #stringToParse VARCHAR(MAX) = 'James has been well received, and should be helped when ever he finds it hard to speak'
Select [dbo].[randomstring] ( #stringToParse )
go
Update SpecialTable
Set SpecialString = [dbo].[randomstring] (SpecialString)
go
*/
declare #StringToreturn varchar(max) = ''
declare #charCounter int = 1
declare #len int = len(#stringToParse)
declare #thisRand int
declare #UpperA int = 65
declare #UpperZ int = 90
declare #LowerA int = 97
declare #LowerZ int = 112
declare #thisChar char(1)
declare #Random_Number float
declare #randomChar char(1)
WHILE #charCounter < #len
BEGIN
SELECT #thisChar = SUBSTRING(#stringToParse, #charCounter, 1)
set #randomChar = #thisChar
--print #randomChar
SELECT #Random_Number = dbo.RandFn()
--print #Random_Number
--only swap if a-z or A-Z
if ASCII(#thisChar) >= #UpperA and ASCII(#thisChar) <= #UpperZ begin
--upper case
set #thisRand = #UpperA + (#Random_Number * convert(float, (#UpperZ-#UpperA)))
set #randomChar = CHAR(#thisRand)
--print #thisRand
end
if ASCII(#thisChar) >= #LowerA and ASCII(#thisChar) <= #LowerZ begin
--upper case
set #thisRand = #LowerA + (#Random_Number * convert(float, (#LowerZ-#LowerA)))
set #randomChar = CHAR(#thisRand)
end
--print #thisRand
--print #randomChar
set #StringToreturn = #StringToreturn + #randomChar
SET #charCounter = #charCounter + 1
END
--Select * from #returnList
return #StringToreturn
END
GO
I am doing some performance testing on a SQL sproc and just want to bang out a quick data generator for testing.
I am after a simple way to generate a pseudo random (true random not needed in this case) varchar field.
Ideas I have so far is having a character definition of valid characters that can be used and then build the string from this definition and use a pseudo random length for length variation with a max/min length defined.
Edit:
My test data generator:
DECLARE #MyDataTable TABLE
(
RecID int IDENTITY(1,1) PRIMARY KEY,
SomeText varchar(255)
)
DECLARE #RecId int, #SomeText varchar(255),
#maxlength int, #minlength int,
#RecordCount int, #Counter int
SET #maxlength = 254
SET #minlength = 50
SET #RecordCount = 500000
SET #Counter = 1
WHILE (#Counter < #RecordCount)
BEGIN
INSERT INTO #MyDataTable
(
SomeText
)
SELECT TOP 1
(
select top (abs(checksum(newid())) % (#maxlength-#minlength) + #minlength) char(abs(checksum(newid())) % 26 + ascii('A'))
from sys.all_objects a1
where sign(a1.object_id) = sign(t.object_id) /* Meaningless thing to force correlation */
for xml path('')
) as NewRandomString
FROM sys.all_objects t;
SET #Counter = #Counter + 1
END
I wrote a blog post on this recently.
http://msmvps.com/blogs/robfarley/archive/2009/12/07/randomising-data.aspx
select top (#stringlength) char(abs(checksum(newid())) % 26 + ascii('A'))
from sys.all_objects
for xml path('')
;
Edit: Sorry - didn't include the random length thing...
SELECT
(
select top (abs(checksum(newid())) % (#maxlength-#minlength) + #minlength) char(abs(checksum(newid())) % 26 + ascii('A'))
from sys.all_objects
for xml path('')
) as NewRandomString
FROM yourTable; /* Maybe something like dbo.nums? */
Edit: Sorry - needs to be correlated...
SELECT
(
select top (abs(checksum(newid())) % (#maxlength-#minlength) + #minlength) char(abs(checksum(newid())) % 26 + ascii('A'))
from sys.all_objects a1
where sign(a1.object_id) = sign(t.object_id) /* Meaningless thing to force correlation */
for xml path('')
) as NewRandomString
,*
FROM sys.all_objects t;
For SQL Server 2008
SELECT
--fixed length
CAST(CRYPT_GEN_RANDOM(50) AS varchar(100)),
--variable length
CAST(CRYPT_GEN_RANDOM(ABS(CHECKSUM(NEWID()))%50) AS varchar(100))
Samples:
r¡Ñ”ã8Ò¯wß×1W=ýÎÜTÜN:Læ*é=Öô/qAtmտ׌1):¢ìèð’¾N
mÁBòºÇòWãmßyWßðÛ2ﬔœ¹t ¦2›ÏÀë?î7Ä›››ºªb
My evil twin wants to use this as a password generator...
This will generate a random string of variable length.
DECLARE #text nvarchar(255),
#length int,
#i int;
SET #i = 0
SET #text = ''
SET #length = RAND() * 50 + 215
WHILE (#i < #length)
BEGIN
SET #text = #text + CHAR(RAND() * 26 + 65)
SET #i = #i + 1
END
If you need it quick or you don't want to do it yourself,
you can also use the tool from
http://www.generatedata.com/
but you can only generate 100 rows if you are just using the online demo.
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?