MSSQL - Create table function, return substring - sql

I need to create this table function. The function needs to return single words from passed parameters like: hello, hhuu, value
The table function should return:
hello,
hhuu,
value
But I am always getting some errors, please could you help me?

you can write as:
DECLARE #input_char VARCHAR(255)
SET #input_char = 'hello, hhuu, value'
;WITH cte AS (
SELECT
CAST('<r>' + REPLACE(#input_char, ' ', '</r><r>') + '</r>' AS XML) AS input_char
)
SELECT
rtrim( LTRIM (xTable.xColumn.value('.', 'VARCHAR(MAX)')) ) AS input_char
FROM cte
CROSS APPLY input_char.nodes('//r') AS xTable(xColumn)

Please give a look at this article:
http://www.codeproject.com/Tips/625872/Convert-a-CSV-delimited-string-to-table-column-in
and you could use ' ' (space) as delimiter.
SELECT * FROM dbo.CSVtoTable('hello, hhuu, value', ' ')

I have used the following function many times. It is a bit lengthy forgive me, but it has become a great tool for me.
CREATE Function [dbo].[ParseText2Table]
(
#p_SourceText varchar(MAX)
,#p_Delimeter varchar(100) = ',' --default to comma delimited.
)
RETURNS #retTable TABLE
(
POSITION INT
,Int_Value bigint
,Num_value REAL--Numeric(18,3)
,txt_value varchar(MAX)
)
AS
BEGIN
DECLARE #tmpTable TABLE
(
Position2 INT IDENTITY(1,1) PRIMARY KEY
,Int_Value bigint
,Num_value REAL--Numeric(18,3)
,txt_value varchar(MAX)
)
DECLARE #w_Continue INT
,#w_StartPos INT
,#w_Length INT
,#w_Delimeter_pos INT
,#w_tmp_int bigint
,#w_tmp_num REAL--numeric(18,3)
,#w_tmp_txt varchar(MAX)
,#w_Delimeter_Len INT
IF len(#p_SourceText) = 0
BEGIN
SET #w_Continue = 0 -- force early exit
END
ELSE
BEGIN
-- if delimiter is ' ' change
IF #p_Delimeter = ' '
BEGIN
SET #p_SourceText = replace(#p_SourceText,' ','ÿ')
SET #p_Delimeter = 'ÿ'
END
-- parse the original #p_SourceText array into a temp table
SET #w_Continue = 1
SET #w_StartPos = 1
SET #p_SourceText = RTRIM( LTRIM( #p_SourceText))
SET #w_Length = DATALENGTH( RTRIM( LTRIM( #p_SourceText)))
SET #w_Delimeter_Len = len(#p_Delimeter)
END
WHILE #w_Continue = 1
BEGIN
SET #w_Delimeter_pos = CHARINDEX( #p_Delimeter
,(SUBSTRING( #p_SourceText, #w_StartPos
,((#w_Length - #w_StartPos) + #w_Delimeter_Len)))
)
IF #w_Delimeter_pos > 0 -- delimeter(s) found, get the value
BEGIN
SET #w_tmp_txt = LTRIM(RTRIM( SUBSTRING( #p_SourceText, #w_StartPos
,(#w_Delimeter_pos - 1)) ))
IF dbo.isReallyNumeric(#w_tmp_txt) = 1 --and not #w_tmp_txt in('.', '-', '+', '^')
BEGIN
--set #w_tmp_int = cast( cast(#w_tmp_txt as real) as bigint)--numeric) as bigint)
SET #w_tmp_int = CASE WHEN (CAST(#w_tmp_txt AS REAL) BETWEEN -9223372036854775808 AND 9223372036854775808) THEN CAST( CAST(#w_tmp_txt AS REAL) AS bigint) ELSE NULL END
SET #w_tmp_num = CAST( #w_tmp_txt AS REAL)--numeric(18,3))
END
ELSE
BEGIN
SET #w_tmp_int = NULL
SET #w_tmp_num = NULL
END
SET #w_StartPos = #w_Delimeter_pos + #w_StartPos + (#w_Delimeter_Len- 1)
END
ELSE -- No more delimeters, get last value
BEGIN
SET #w_tmp_txt = LTRIM(RTRIM( SUBSTRING( #p_SourceText, #w_StartPos
,((#w_Length - #w_StartPos) + #w_Delimeter_Len)) ))
IF dbo.isReallyNumeric(#w_tmp_txt) = 1 --and not #w_tmp_txt in('.', '-', '+', '^')
BEGIN
--set #w_tmp_int = cast( cast(#w_tmp_txt as real) as bigint)--as numeric) as bigint)
SET #w_tmp_int = CASE WHEN (CAST(#w_tmp_txt AS REAL) BETWEEN -9223372036854775808 AND 9223372036854775808) THEN CAST( CAST(#w_tmp_txt AS REAL) AS bigint) ELSE NULL end
SET #w_tmp_num = CAST( #w_tmp_txt AS REAL)--numeric(18,3))
END
ELSE
BEGIN
SET #w_tmp_int = NULL
SET #w_tmp_num = NULL
END
SELECT #w_Continue = 0
END
INSERT INTO #tmpTable VALUES( #w_tmp_int, #w_tmp_num, #w_tmp_txt )
END
INSERT INTO #retTable SELECT Position2, Int_Value ,Num_value ,txt_value FROM #tmpTable
RETURN
END
Here are the supporting functions for above as well:
CREATE FUNCTION dbo.isReallyInteger
(
#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
CREATE FUNCTION dbo.isReallyNumeric
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
DECLARE #pos TINYINT
SET #pos = 1 + LEN(#num) - CHARINDEX('.', REVERSE(#num))
RETURN CASE
WHEN PATINDEX('%[^0-9.-]%', #num) = 0
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
AND
(
((#pos = LEN(#num)+1)
OR #pos = CHARINDEX('.', #num))
)
THEN
1
ELSE
0
END
END
Usage Examples:
--Single Character Delimiter
--select * from dbo.ParseText2Table('100|120|130.56|Yes|Cobalt|Blue','|')
--select txt_value from dbo.ParseText2Table('100 120 130.56 Yes Cobalt Blue',' ') where Position = 3
--select * from dbo.ParseText2Table('100,120,130.56,Yes,Cobalt,Blue,,',',')
/*
POSITION Int_Value Num_value txt_value
----------- ----------- -------------------- --------------
1 100 100.000 100
2 120 120.000 120
3 131 130.560 130.56
4 NULL NULL Yes
5 NULL NULL Cobalt Blue
*/

Related

Replace the alternate occurances of a substring

My input strings are like:
A or B OR C or D OR E or F
A OR B OR C OR D OR E OR F
Expected Output: 'A or B' OR 'C or D' OR 'E or F'
outputString = '''' + REPLACE(#inputValue COLLATE Latin1_General_CS_AS, ' OR ' COLLATE Latin1_General_CS_AS, ''' OR ''') + ''''
I tried using SQL Replace function and the above statement works properly for the first string and I get the desired output but for the second string since we have all the ORs in the uppercase it fails and returns 'A' OR 'B' OR 'C' OR 'D' OR 'E' OR 'F'
I'm using SSMS 15.0.
How can I solve this problem? Any help will be appreciated.
Here's a solution that uses a UDF.
The function splits a string on a pattern as a resultset.
(similar as the STRING_SPLIT function, but with a pattern)
The FOR XML trick is then used to construct a string from the splitted parts, and to add the quotes.
DECLARE #vchNewValue VARCHAR(100), #result VARCHAR(100);
SET #vchNewValue = 'A OR B or C OR D or E OR F';
SET #result = LTRIM(RTRIM((
SELECT
CASE WHEN match = 1
THEN ' '+quotename(ltrim(rtrim(replace(value,' OR ',' or ') )),'''')+' '
ELSE UPPER(value)
END
FROM dbo.fnPattern_Split(' '+#vchNewValue+' ', ' % OR % ') AS spl
ORDER BY ordinal
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
));
SELECT #result AS result;
result
'A or B' OR 'C or D' OR 'E or F'
Test db<>fiddle here
The UDF
Uses PATINDEX to find each next start position of the given pattern in the string.
Then finds the nearest end position where the pattern is still valid.
So it's kinda like a lazy search in regex.
The positions are then used to insert the parts into the returned table.
CREATE FUNCTION dbo.fnPattern_Split
(
#str VARCHAR(MAX),
#pattern VARCHAR(100)
)
RETURNS #tbl TABLE (
ordinal INT,
value VARCHAR(MAX),
match BIT
)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #value NVARCHAR(MAX)
, #splitvalue NVARCHAR(MAX)
, #startpos INT = 0
, #endpos INT = 0
, #ordinal INT = 0
, #foundend BIT = 0
, #patminlen INT = ISNULL(NULLIF(LEN(REPLACE(#pattern,'%','')),0),1);
WHILE (LEN(#str) > 0)
BEGIN
SET #startpos = ISNULL(NULLIF(PATINDEX('%'+#pattern+'%', #str),0), LEN(#str)+1);
IF #startpos < LEN(#str)
BEGIN
SET #foundend = 0;
SET #endpos = #startpos+#patminlen-1;
WHILE #endpos < LEN(#str) AND #foundend = 0
BEGIN
IF SUBSTRING(#str, #startpos, 1+#endpos-#startpos) LIKE #pattern
SET #foundend = 1;
ELSE
SET #endpos += 1;
END
END
ELSE SET #endpos = LEN(#str);
IF #startpos > 1
BEGIN
SET #ordinal += 1;
SET #value = LEFT(#str, #startpos-1);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #value, 0);
END
IF #endpos >= #startpos
BEGIN
SET #ordinal += 1;
SET #splitvalue = SUBSTRING(#str, #startpos, 1+#endpos-#startpos);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #splitvalue, 1);
END
SET #str = SUBSTRING(#str, #endpos+1, LEN(#str));
END;
RETURN;
END;
A recursive solution that stuffs the quotes.
The recursive CTE loops through the string while finding the start positions of the ' or ' patterns.
Since ' or ' has 4 characters, having a start position means you also have the end position.
TheSTUFF function can insert characters in a string on positions.
So the positions are used to stuff the quotes where needed.
Which is every even occurence (modulus 2 of lvl is 0).
declare #input varchar(100)
, #result varchar(100);
set #input = 'A OR B or C OR D or E OR F';
set #result = #input;
with rcte as (
select 1 as lvl
, charindex(' or ', #input) as pos
, len(#input) as max_pos
union all
select lvl+1
, isnull(nullif(charindex(' or ', #input, pos+4), 0), max_pos)
, max_pos
from rcte
where pos < max_pos
)
select #result = stuff(stuff(#result,pos+4,0,''''),pos,0,'''')
from rcte
where lvl%2 = 0 and pos+4 < max_pos
order by lvl desc;
SET #result = ''''+#result+'''';
SET #result = REPLACE(REPLACE(#result,' OR ',' or '),''' or ''',''' OR ''');
select #result as result;
result
'A or B' OR 'C or D' OR 'E or F'
Test on db<>fiddle here
This solution comes with a high cost on server load since while is used.
declare #input varchar(100)
set #input = 'A or B or C or D or E or F or G or'
declare #inc int = 1, #end int = 1
,#final varchar(100) = '', #part varchar(100)
,#nextposition varchar(100), #or varchar(10)= ''
,#last varchar(10), #ifendsOR varchar(10)
select #nextposition = case when #input like '%or' then substring(#input,1,len(#input)-2) else #input end
select #ifendsOR = case when #input like '%or' then ' or' else '' end
select #last = ltrim(rtrim(right(#nextposition,2)))
while #end <> 0
begin
select #part = substring(#nextposition,1,charindex('or',#nextposition)-2)
select #nextposition = replace(#nextposition,concat(#part,' or '),'')
set #end = charindex('or',#nextposition)
select #or = case when #inc%2 = 0 then ' OR ' else ' or ' end
set #inc = #inc+1
set #final = concat(#final,#part,#or)
end
select #ifendsOR = case when #inc%2 = 0 then upper(#ifendsOR) else #ifendsOR end
select concat(#final,#last,#ifendsOR)

Check a chunk of text for tilde and ensure they are 60 characters apart. If not place a tilde inbetween

I currently have a function in SQL that places a tilde every 60 characters, but the original text already has tilde's so , basically I want to change that if there is a tilde and the next tilde is under 60 characters away, then skip to the next tilde. If its over 60 characters, then only then place a extra tilde.
My current function looks like;
function [dbo].[AddTilde] (
#string varchar(max),
#count int
)
returns varchar(max)
as
begin
declare #result varchar(max) = ''
declare #token varchar(max) = ''
while DATALENGTH(#string) > 0
begin
select #token = left(#string, #count)
select #string = REPLACE(#string, #token, '')
select #result +=#token +case when DATALENGTH(#string)=0 then '' else '~' end
end
return #result
end
Any help appreciated
Many Thanks
DECLARE #string1 VARCHAR(max),
#string2 VARCHAR(max) = '',
#i1 INT,
#i2 INT
SET #string1 = '12345678901234567890~1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
WHILE LEN(#string1) > 0
BEGIN
SET #i1 = CHARINDEX('~',#string1)
SET #i2 = #i1 - (ABS(#i1-60) + (#i1-60)) / 2 -- MINIMUM OF ~ LOCATION AND 60
SET #i1 = LEN(#string1)
IF #i2 = 0 SET #i2 = #i1 - (ABS(#i1-60) + (#i1-60)) / 2 -- MINIMUM OF LENGTH OF #string1 LOCATION AND 60
IF #i2 < 60
BEGIN
SET #string2 = #string2 + LEFT(#string1,#i2)
SET #string1 = RIGHT(#string1,#i1-#i2)
END
ELSE
BEGIN
SET #string2 = #string2 + LEFT(#string1,60) + '~'
SET #string1 = RIGHT(#string1,#i1-60)
END
END
Results: 12345678901234567890~123456789012345678901234567890123456789012345678901234567890~1234567890123456789012345678901234567890
This is based on my String Splitting function, which has a very good performance.
This function should be quite efficient, albeit hard to understand (I have added a few commenets in attempt to make it easier).
You can change internal parameters easily e.g. #Delimitor can be multiple characters
Test cases are included at the bottom.
ALTER FUNCTION [dbo].[AddTilde]
(
#String VARCHAR( MAX ),
#Count INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #CurrentPosition BIGINT, #NextDelimiterPosition BIGINT, #NextFixedPosition BIGINT, #NextPosition BIGINT,
#DelimiterLength INT, #Delimiter VARCHAR( 5 ), #Result VARCHAR( MAX )
-- Initialise
SET #Delimiter = '~'
SET #DelimiterLength = LEN( #Delimiter )
SET #Result = ''
-- Ensures we can terminate loop without using an extra IF condition, saves a few = 0 checks
SET #String = #String + #Delimiter
SET #CurrentPosition = 1
-- Check for next Tilde position
SET #NextDelimiterPosition = CHARINDEX( #Delimiter, #String, #CurrentPosition )
-- Initialise fixed increment position
SET #NextFixedPosition = #Count
-- Compare, which one is earlier and use that one. Handle cases where the next token begins with Tilde
SET #NextPosition = CASE WHEN #NextDelimiterPosition - #DelimiterLength > #NextFixedPosition THEN #NextFixedPosition ELSE #NextDelimiterPosition END
WHILE #NextDelimiterPosition > 0
BEGIN
SET #Result = #Result + SUBSTRING( #String, #CurrentPosition, #NextPosition - #CurrentPosition + 1 )
-- Handle cases where the next token begins with Tilde and avoids doubling up Tildes
+ ( CASE WHEN #NextPosition = #NextDelimiterPosition THEN '' ELSE #Delimiter END )
SET #CurrentPosition = #NextPosition + 1
-- Increment fixed position
SET #NextFixedPosition = #CurrentPosition + #Count - 1
-- Check for next Tilde position
SET #NextDelimiterPosition = CHARINDEX( #Delimiter, #String, #CurrentPosition )
SET #NextPosition = CASE WHEN #NextDelimiterPosition - #DelimiterLength > #NextFixedPosition THEN #NextFixedPosition ELSE #NextDelimiterPosition END
END
-- Remove trailing Tilde
SET #Result = SUBSTRING( #Result, 1, LEN( #Result ) - #DelimiterLength )
RETURN #Result
END
/* Test Cases
SELECT dbo.[AddTilde]( 'ab~c~defghijkl~', 3 ) --> 'ab~c~def~ghi~jkl~'
SELECT dbo.[AddTilde]( '~ab~c~defghijkl', 3 ) --> '~ab~c~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'ab~c~~defghijkl', 3 ) --> 'ab~c~~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'abcdefghijkl', 3 ) --> 'abc~def~ghi~jkl'
SELECT dbo.[AddTilde]( 'a', 3 ) --> 'a'
*/

search in a string creditcard numeric value

I want to find a credit card numeric value in a sql string.
for example;
DECLARE #value1 NVARCHAR(MAX) = 'The payment is the place 1234567812345678'
DECLARE #value2 NVARCHAR(MAX) = 'The payment is the place 123456aa7812345678'
DECLARE #value3 NVARCHAR(MAX) = 'The payment1234567812345678is the place'
The result should be :
#value1Result 1234567812345678
#value2Result NULL
#value3Result 1234567812345678
16 digits must be together without space.
How to do this in a sql script or a function?
edit :
if I want to find these 2 credit card value.
#value4 = 'card 1 is : 4034349183539301 and the other one is 3456123485697865'
how should I implement the scripts?
You can use PathIndex as
PATINDEX('%[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%', yourStr)
if the result is 0 then it doesnt containg 16 digits other was it contains.
It can be used withing a Where statement or Select statement based on your needs
You can write as:
SELECT case when Len(LEFT(subsrt, PATINDEX('%[^0-9]%', subsrt + 't') - 1)) = 16
then LEFT(subsrt, PATINDEX('%[^0-9]%', subsrt + 't') - 1)
else ''
end
FROM (
SELECT subsrt = SUBSTRING(string, pos, LEN(string))
FROM (
SELECT string, pos = PATINDEX('%[0-9]%', string)
FROM table1
) d
) t
Demo
DECLARE #value1 NVARCHAR(MAX) = 'card 1 is : 4034349183539301 and the other one is 3456123485697865'
DECLARE #Lenght INT
,#Count INT
,#Candidate CHAR
,#cNum INT
,#result VARCHAR(16)
SELECT #Count = 1
SELECT #cNum = 0
SELECT #result = ''
SELECT #Lenght = LEN(#value1)
WHILE #Count <= #Lenght
BEGIN
SELECT #Candidate = SUBSTRING(#value1, #Count, 1)
IF #Candidate != ' '
AND ISNUMERIC(#Candidate) = 1
BEGIN
SET #cNum = #cNum + 1
SET #result = #result + #Candidate
END
ELSE
BEGIN
SET #cNum = 1
SET #result = ''
END
IF #cNum > 16
BEGIN
SELECT #result 'Credit Number'
END
SET #Count = #Count + 1
END
There you go kind sir.
DECLARE
#value3 NVARCHAR(MAX) = 'The payment1234567812345678is the place',
#MaxCount int,
#Count int,
#Numbers NVARCHAR(100)
SELECT #Count = 1
SELECT #Numbers = ''
SELECT #MaxCount = LEN(#value3)
WHILE #Count <= #MaxCount
BEGIN
IF (UNICODE(SUBSTRING(#value3,#Count,1)) >= 48 AND UNICODE(SUBSTRING(#value3,#Count,1)) <=57)
SELECT #Numbers = #Numbers + SUBSTRING(#value3,#Count,1)
SELECT #Count = #Count + 1
END
PRINT #Numbers
You can make this as a function if you are planning to use it a lot.

SQL proc to calculate check digit for 7 and 12 digit upc

I just had to scour the internet for this code yet again so I figured I would put it here so I can find it a little faster the next time and hopefully you found it a little faster too :)
Check this.
The code below can check digit in all GTIN's (EAN8, EAN13, EAN14, UPC/A, UPC/E)
CREATE FUNCTION [dbo].[check_digit]
(
#GTIN VARCHAR(14)
)
RETURNS TINYINT
AS
BEGIN
DECLARE #Index TINYINT,
#Multiplier TINYINT,
#Sum TINYINT,
#checksum TINYINT,
#result TINYINT,
#GTIN_strip VARCHAR(13)
SELECT #GTIN_strip = SUBSTRING(#GTIN,1,LEN(#GTIN)-1);
SELECT #Index = LEN(#GTIN_strip),
#Multiplier = 3,
#Sum = 0
WHILE #Index > 0
SELECT #Sum = #Sum + #Multiplier * CAST(SUBSTRING(#GTIN_strip, #Index, 1) AS TINYINT),
#Multiplier = 4 - #Multiplier,
#Index = #Index - 1
SELECT #checksum = CASE #Sum % 10
WHEN 0 THEN '0'
ELSE CAST(10 - #Sum % 10 AS CHAR(1))
END
IF (SUBSTRING(#GTIN,LEN(#GTIN),1) = #checksum)
RETURN 1; /*true*/
RETURN 0; /*false*/
END
CREATE FUNCTION [dbo].[fn_EAN13CheckDigit] (#Barcode nvarchar(12))
RETURNS nvarchar(13)
AS
BEGIN
DECLARE #SUM int , #COUNTER int, #RETURN varchar(13), #Val1 int, #Val2 int
SET #COUNTER = 1 SET #SUM = 0
WHILE #Counter < 13
BEGIN
SET #VAL1 = SUBSTRING(#Barcode,#counter,1) * 1
SET #VAL2 = SUBSTRING(#Barcode,#counter + 1,1) * 3
SET #SUM = #VAL1 + #SUM
SET #SUM = #VAL2 + #SUM
SET #Counter = #Counter + 2;
END
SET #SUM=(10-(#SUM%10))%10
SET #Return = #BARCODE + CONVERT(varchar,((#SUM)))
RETURN #Return
END
And here's a MySQL implementation as well.
Note it requires a 13 digit UPC/EAN13 as input, so you should LPAD your input as required.
drop function if exists barcodify;
delimiter //
at the last digit.
create function barcodify(upc varchar(15))
returns varchar(15)
sql security invoker
begin
declare i, r, odd, even,result int;
set odd=0;
set even =0;
set i = length(upc);
while i > 0 do
set r = substring(upc, i, 1) ;
if(i % 2 >0) then set odd = odd + r;
else set even = even + r;
end if;
set i = i - 1;
end while;
set result = (3*odd)+even;
if result % 10 =0 then return 0;
else return (10 - (result %10));
end if;
end //
delimiter ;
Here's my implementation of a function in Oracle.
CREATE OR REPLACE FUNCTION GenerateCheckDigit(pUPC IN VARCHAR2) RETURN INT
IS
vCheckDigit INT;
BEGIN
WITH barcodeData AS
(
SELECT pUPC barcode FROM dual
)
SELECT
10-REPLACE(MOD(SUM(SUBSTR(barcode, -LEVEL, 1) * DECODE(MOD(LEVEL-1, 2), 1, 1, 3)),10),0,10) INTO vCheckDigit
FROM barcodeData
CONNECT BY LEVEL <= LENGTH(barcode);
RETURN vCheckDigit;
END;
To calculate the last digit.
https://segovoni.medium.com/sql-server-function-to-calculate-gs1-barcode-check-digit-d55b478ff645
CREATE FUNCTION dbo.udf_GetGS1EAN13CheckDigit
(
#ACode AS VARCHAR(12)
)
RETURNS SMALLINT
AS BEGIN
/*
Author: Sergio Govoni
Notes: Calculate the check-digit of a GS1 EAN13 code
Version: 1.0
*/
DECLARE
#tmpCode AS VARCHAR(12)
,#tmpMulSup AS VARCHAR(8000)
,#tmp AS VARCHAR(8000)
,#i AS INT
,#j AS INT
,#z AS INT
,#SumDEven AS INT
,#SumDOdd AS INT
,#List AS VARCHAR(8000)
,#tmpList AS VARCHAR(8000)
,#CheckSum AS SMALLINT
SET #SumDEven = 0
SET #SumDOdd = 0
SET #List = ''
SET #tmpList = ''
SET #tmp = ''
SET #tmpCode = #ACode
/* 0. List builder */
SET #j = LEN(#tmpCode) + 1
SET #i = 1
WHILE (#i <= LEN(#tmpCode)) BEGIN SET #List = #List + '|' + LTRIM(RTRIM(STR(#j))) + ';' + SUBSTRING(#tmpCode, #i, 1) SET #j = (#j - 1) SET #i = (#i + 1) END /* 1. Add up the digits in even position */ SET #i = 1 SET #tmpList = #List WHILE (CHARINDEX('|', #tmpList) > 0)
BEGIN
SET #j = CHARINDEX('|', #tmpList)
SET #z = CHARINDEX(';', #tmpList)
IF (CAST(SUBSTRING(#tmpList, (#j + 1), (#z - (#j + 1))) AS INTEGER) % 2) = 0
BEGIN
SET #SumDEven = #SumDEven + CAST(SUBSTRING(#tmpList, (#z + 1), 1) AS INTEGER)
END
SET #tmpList = SUBSTRING(#tmpList, (#z + 2), LEN(#tmpList))
END
/* 2. Multiply the result of the previous step (the first step) to 3 (three) */
SET #SumDEven = (#SumDEven * 3)
/* 3. Add up the digits in the odd positions */
SET #i = 1
SET #tmpList = #List
WHILE (CHARINDEX('|', #tmpList) > 0)
BEGIN
SET #j = CHARINDEX('|', #tmpList)
SET #z = CHARINDEX(';', #tmpList)
IF (CAST(SUBSTRING(#tmpList, (#j + 1), (#z - (#j + 1))) AS INTEGER) % 2) <> 0
BEGIN
SET #SumDOdd = #SumDOdd + CAST(SUBSTRING(#tmpList, (#z + 1), 1) AS INTEGER)
END
SET #tmpList = SUBSTRING(#tmpList, (#z + 2), LEN(#tmpList))
END
/* 4. Add up the results obtained in steps two and three */
SET #CheckSum = (#SumDEven + #SumDOdd)
/* 5. Subtract the upper multiple of 10 from the result obtained in step four */
IF ((#CheckSum % 10) = 0)
BEGIN
/* If the result of the four step is a multiple of Ten (10), like
Twenty, Thirty, Forty and so on,
the check-digit will be equal to zero, otherwise the check-digit will be
the result of the fifth step
*/
SET #CheckSum = 0
END
ELSE BEGIN
SET #tmpMulSup = LTRIM(RTRIM(STR(#CheckSum)))
SET #i = 0
WHILE #i <= (LEN(#tmpMulSup) - 1)
BEGIN
SET #tmp = #tmp + SUBSTRING(#tmpMulSup, #i, 1)
IF (#i = LEN(#tmpMulSup) - 1)
BEGIN
SET #tmp = LTRIM(RTRIM(STR(CAST(#tmp AS INTEGER) + 1)))
SET #tmp = #tmp + '0'
END
SET #i = (#i + 1)
END
SET #CheckSum = CAST(#tmp AS INTEGER) - #CheckSum
END
RETURN #CheckSum
END;
CREATE FUNCTION sfn_ean_chkdigit(#barcode varchar(20))
RETURNS CHAR(1)
AS
BEGIN
DECLARE #chk_digit int, #chk int
DECLARE #num TABLE (num int)
IF LEN(#barcode) NOT IN (7, 12) RETURN NULL
INSERT INTO #num
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
SELECT #chk_digit = SUM(CONVERT(int, SUBSTRING(#barcode, LEN(#barcode) - num + 1, 1)) * CASE WHEN num % 2 = 1 THEN 3 ELSE 1 END)
FROM #num
WHERE num <= LEN(#barcode)
SELECT #chk_digit = (10 - (#chk_digit % 10)) % 10
RETURN CHAR(ASCII('0') + #chk_digit)
END
Num unico script
select lpad('123456789012',12,'0') || ( (r+1)*10 - soma )%10
from (select sum(v::int * case when i%2=0 then 3 else 1 end) soma,
round(sum(v::int * case when i%2=0 then 3 else 1 end)/10.0) r
from unnest(string_to_array(lpad('123456789012',12,'0'),null)) with ordinality d(v,i)
) a

Have someone a function for SQL Server that given a pattern and a value could return a normalized value

I'm programming a function in SQL 2008R2 that i could give it some parameters like a value varchar, a pattern varchar, a separator char and a filler also char. Then I would like to give the value '22687' with the patter '000.000.000.000', a separator '.' and the filler would be '0', then i would like to expect the function will return '000.000.022.687', does any one have a function already done that can do this?
Something like this:
DECLARE #valor VARCHAR(30)
DECLARE #formato VARCHAR(30)
DECLARE #separador CHAR(1)
DECLARE #rellenarcon CHAR(1)
SELECT #valor = '22959'
SELECT #formato = '000.000.000.000'
SELECT #separador = '.'
SELECT #rellenarcon = '0'
DECLARE #n INTEGER
DECLARE #m INTEGER
DECLARE #i INTEGER
DECLARE #j INTEGER
SELECT #n = LEN(#formato)
SELECT #m = LEN(#valor)
SELECT #i = 1
SELECT #j = 1
DECLARE #res2 varchar(30)
SELECT #res2 = ''
SELECT #valor = REVERSE(#valor)
WHILE #i<=#n
BEGIN
if SUBSTRING(#formato,#i,1) <> #separador
begin
IF #j<=#m
BEGIN
SELECT #res2 = #res2 + SUBSTRING(#valor,#j,1)
SELECT #i=#i+1
SELECT #j=#j+1
END
ELSE
BEGIN
SELECT #res2 = #res2 + #rellenarcon
SELECT #i=#i+1
END
end
else
BEGIN
SELECT #res2 = #res2 + #separador
SELECT #i=#i+1
END
END
print reverse(#res2)
Is a crossover code from java to tsql, the original code in java is:
public static String formatear(String valor, String formato, char separator,
char fillWith, Map<Integer, String> params) {
int n = formato.length() - 1;
int m = valor.length() - 1;
int i = n;
int j = m;
StringBuilder res = new StringBuilder(formato);
for(; i >= 0; i--) {
if(res.charAt(i) != separator) {
if(j >= 0) {
res.deleteCharAt(i);
res.insert(i, valor.charAt(j--));
} else {
res.deleteCharAt(i);
res.insert(i, fillWith);
}
}
}
if(params != null) {
Set<Integer> keys = params.keySet();
for(Integer key : keys) {
i = key;
res.deleteCharAt(i);
res.insert(i, params.get(key));
}
}
return res.toString();
}
The following assumes well-formed inputs, e.g. the value is not longer than the pattern.
declare #Pattern as VarChar(64) = '000.000.000.000';
declare #Fill as Char = '0';
declare #Value as VarChar(64) = '22687';
declare #False as Bit = 0;
declare #True as Bit = 1;
with Gargoyle as (
select #Pattern as Pattern, #Value as Value, Cast( '' as VarChar(64) ) as Buffer,
case when Right( #Pattern, 1 ) = #Fill then #True else #False end as Fill
union all
select
-- Always consume a character from the pattern.
Left( Pattern, Len( Pattern ) - 1 ),
-- Consume a character from the value if the pattern contains fill at the current position.
case
when Fill = #True and Value != '' then Left( Value, Len( Value ) - 1 )
else Value end,
-- Add the correct character to the buffer.
Cast( case when Fill = #True and Value != '' then Right( Value, 1 ) else Right( Pattern, 1 ) end + Buffer as VarChar(64) ),
-- Check the next pattern character for fill.
case
when Len( Pattern ) = 1 then #False
when Substring( Pattern, Len( Pattern ) - 1, 1 ) = #Fill then #True
else #False end
from Gargoyle
where Pattern != ''
)
select Buffer
from Gargoyle
where Pattern = '';
Or, as a function:
create function dbo.PatternFill( #Pattern as VarChar(64), #Fill as Char, #Value as VarChar(64) )
returns VarChar(64)
as
begin
declare #Buffer as VarChar(64) = ''
declare #PatternChar as Char = Right( #Pattern, 1 )
declare #ValueChar as Char = Right( #Value, 1 )
while #Pattern != ''
begin
if #PatternChar = #Fill and #ValueChar != ''
begin
-- Replace a fill character with a value character.
select #Buffer = #ValueChar + #Buffer
if Len( #Value ) > 1
select #Value = Left( #Value, Len( #Value ) - 1 ), #ValueChar = Right( #Value, 1 )
else
select #ValueChar = '', #Value = ''
end
else
begin
-- Copy the pattern character.
select #Buffer = #PatternChar + #Buffer
end
if Len( #Pattern ) > 1
select #Pattern = Left( #Pattern, Len( #Pattern ) - 1 ), #PatternChar = Right( #Pattern, 1 )
else
select #PatternChar = '', #Pattern = ''
end
return #Buffer
end
go
declare #Result as VarChar(64)
declare #Count as Int = 1000000
declare #Start as DateTime = GetDate()
while #Count > 0
select #Result = dbo.PatternFill( '000.000.000.000', '0', '22687' ), #Count = #Count - 1
select #Result as [Result], DateDiff( ms, #Start, GetDate() ) as [Total ms]
1,000,000 iterations on my notebook took 151,656ms, but it's busy BOINCing. That's simple timing with no correction for the time consumed by an empty loop or calling an empty function.
This query will do it:
;with cteZeroPadded(Num) as
(
Select Right('000000000000' + '22687', 12)
)
,cteSplit as
(
Select SUBSTRING(Num, 1, 3) Col1
,SUBSTRING(Num, 4, 3) Col2
,SUBSTRING(Num, 7, 3) Col3
,SUBSTRING(Num, 10, 3) Col4
From cteZeroPadded
)
Select Col1 + '.' + Col2 + '.' + Col3 + '.' + Col4
From cteSplit
In SQLServer2005+ you can use option with recursive CTE
DECLARE #valor varchar(30) = '22959',
#formato varchar(30) = '000000000000000',
#text varchar(30),
#result varchar(30) = N''
SET #text = REVERSE(RIGHT(#formato + #valor, 15))
;WITH cte AS
(
SELECT 1 AS Number, SUBSTRING(#text, 1, 1) AS Num
UNION ALL
SELECT c.Number + 1,
CASE WHEN c.Number IN(3, 7, 11) THEN '.' ELSE
SUBSTRING(#text, CASE WHEN c.Number > 11 THEN c.Number - 2
WHEN c.Number > 7 THEN c.Number - 1
WHEN c.Number > 3 THEN c.Number
ELSE c.Number + 1
END, 1)
END
FROM cte c
WHERE Number < LEN(#text)
)
SELECT #result += c.Num
FROM cte c
ORDER BY Number DESC
SELECT #result
See demo on SQLFiddle