SQL replace every other comma with a semicolon - sql

I have a bunch of strings that should have been stored as value pairs but were not. Now I need to replace every other comma with a semicolon to make them pairs. Hoping to find a simple way of doing this, but there might not be one.
ex:
-1328.89,6354.22,-1283.94,6242.96,-1172.68,6287.91,-1217.63,6399.18
should be:
-1328.89,6354.22;-1283.94,6242.96;-1172.68,6287.91;-1217.63,6399.18

create function f_tst(#a varchar(100)) -- use right size of field
returns varchar(100) -- make sure you use the right size of field
begin
declare #pos int = charindex(',', #a) + 1
;while 0 < charindex(',', #a, #pos)
select #a = stuff(#a, charindex(',', #a, #pos), 1, ';'),
#pos = charindex(',', #a, charindex(',', #a, #pos + 1)) + 1
return #a
end
go
declare #a varchar(100) = '-1328.89,6354.22,-1283.94,6242.96,-1172.68,6287.91,-1217.63,6399.18'
select dbo.f_tst(#a)
Or in your example
update <table>
set <field> = dbo.f_tst(<field>)

Surely not so simple as you want, but a CHARINDEX/SUBSTRING solution:
Declare #input nvarchar(max) = '-1328.89,6354.22,-1283.94,6242.96,-1172.68,6287.91,-1217.63,6399.18'
Declare #i int = 0, #t int = 0, #isComma bit = 1
Declare #output nvarchar(max) = ''
Select #i = CHARINDEX(',', #input)
While (#i > 0)
Begin
Select #output = #output + SUBSTRING(#input, #t + 1, #i - #t - 1) + CASE #isComma WHEN 1 THEN ',' ELSE ';' END
Select #t = #i
Select #i = CHARINDEX(',', #input, #i + 1), #isComma = 1 - #isComma
End
Select #output = #output + SUBSTRING(#input, #t + 1, 1000)
Select #output

This can be done with a combination of dynamic sql and for xml:
declare #sql nvarchar(max)
set #sql = '-1328.89,6354.22,-1283.94,6242.96,-1172.68,6287.91,-1217.63,6399.18'
set #sql = '
select replace((select cast(value as varchar(50)) +
cast(case row_number() over(order by sort)%2 when 0 then '','' else '';'' end as char(1))
from (select ' + replace(#sql,',',' value,1 sort union all select ') + ',1 sort)q
for xml path(''''))+''||'','',||'','''') YourUpdatedValue'
exec(#sql)

This can be done in a single query:
DECLARE #t TABLE (id int, col varchar(max))
INSERT #t VALUES
(1,'-1328.89,6354.22,-1283.94,6242.96,-1172.68,6287.91,-1217.63,6399.18'),
(2,'-4534.89,454.22,-1123.94,2932.96,-1872.68,327.91,-417.63,635.18')
;WITH t AS (
SELECT id, i % 2 x, i / 2 y, val
FROM #t
CROSS APPLY (SELECT CAST('<a>'+REPLACE(col,',','</a><a>')+'</a>' AS xml) xml1 ) t1
CROSS APPLY (
SELECT
n.value('for $i in . return count(../*[. << $i])', 'int') i,
n.value('.','varchar(max)') AS val
FROM xml1.nodes('a') x(n)
) t2
)
SELECT id, y, [0]+','+[1] col
FROM t
PIVOT(MAX([val]) FOR x IN ([0],[1])) t3
ORDER BY id, y
id y val
----------------------------
1 0 -1328.89,6354.22
1 1 -1283.94,6242.96
1 2 -1172.68,6287.91
1 3 -1217.63,6399.18
2 0 -4534.89,454.22
2 1 -1123.94,2932.96
2 2 -1872.68,327.91
2 3 -417.63,635.18

Related

SQL function parsing out text from a column

i have a table with some text i want to parse out and i'm very close to the solution but its not quite there. i have a column called indicatornumerator and i want to parse out similar to below ie i want to each of the values in between the square brackets onto a new row. as you can see from the output below i am getting a repeat for the first value after the minus. this does not happen for those which do not have a minus.
my function is as follows:
alter function fn_breakdown (#string varchar(max))
returns #breakdown table
(
originalstr varchar(max),
breakdownstr varchar(max)
)
as
begin
while charindex('r',#string,1) >0
begin
insert into #breakdown
select #string,
SUBSTRING(#string,CHARINDEX('r',#string,1),CHARINDEX(']',#string,1)-CHARINDEX('r',#string,1))
set #string = right(#string,len(#string)-CHARINDEX(')',#string,1))
end
return
end
a second thing i would like to do is to return those after the minus as a minus value rather eg. -R101 instead of R101 as i need to subtract those values later.
Any help much appreciated
the below should create a temporary table and will show you the output i'm getting. you will see that one of the rows duplicates. i am looking to have one row for each value beginning with 'R' in the indicatornumerator column
create table #temped2
(
indicatornumerator varchar(max)
)
insert into #temped2
select '(SUM([R4]) + SUM([R1010])) - (SUM([R1035]) + SUM([R1034]))'
select * from #temped2
cross apply fn_breakdown(indicatornumerator)
Download a copy of ngrams8k and then:
-- sample data
DECLARE #yourtable TABLE (IndicatorNumerator varchar(1000));
INSERT #yourtable
SELECT 'SUM([R4]) + SUM([R1010]) - SUM([R50]) + SUM([R200])' UNION ALL
SELECT 'SUM([R554]) + SUM([R210]) - SUM([R500]) + SUM([R999])';
-- Solution
SELECT
t.IndicatorNumerator,
breakdownstr = substring(t.IndicatorNumerator, ng.position+1, n.d-ng.position-1)
FROM #yourtable t
CROSS APPLY dbo.ngrams8k(t.IndicatorNumerator, 2) ng
CROSS APPLY (VALUES (charindex(']',t.IndicatorNumerator, ng.position+1))) n(d)
WHERE ng.token = '[R';
Returns:
IndicatorNumerator breakdownstr
-------------------------------------------------------- --------------
SUM([R4]) + SUM([R1010]) - SUM([R50]) + SUM([R200]) R4
SUM([R4]) + SUM([R1010]) - SUM([R50]) + SUM([R200]) R1010
SUM([R4]) + SUM([R1010]) - SUM([R50]) + SUM([R200]) R50
SUM([R4]) + SUM([R1010]) - SUM([R50]) + SUM([R200]) R200
SUM([R554]) + SUM([R210]) - SUM([R500]) + SUM([R999]) R554
SUM([R554]) + SUM([R210]) - SUM([R500]) + SUM([R999]) R210
SUM([R554]) + SUM([R210]) - SUM([R500]) + SUM([R999]) R500
SUM([R554]) + SUM([R210]) - SUM([R500]) + SUM([R999]) R999
Please try this code if you are using SQL Server 2016 and avove
;WITH cte_date
AS
(
SELECT indicatornumerator, REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(indicatornumerator,'(',''),')',''),'[',''),']',''),'SUM',''),'- ','-') ParsedData
FROM #temped2
)
SELECT *
FROM cte_date
CROSS APPLY STRING_SPLIT(ParsedData, ' ')
WHERE [VALUE] != '+';
Below script will work older than sql server 2016
CREATE FUNCTION [dbo].[StrParse]
(#delimiter CHAR(1),
#csv NTEXT)
RETURNS #tbl TABLE(Keys NVARCHAR(255))
AS
BEGIN
DECLARE #len INT
SET #len = Datalength(#csv)
IF NOT #len > 0
RETURN
DECLARE #l INT
DECLARE #m INT
SET #l = 0
SET #m = 0
DECLARE #s VARCHAR(255)
DECLARE #slen INT
WHILE #l <= #len
BEGIN
SET #l = #m + 1--current position
SET #m = Charindex(#delimiter,Substring(#csv,#l + 1,255))
IF #m <> 0
SET #m = #m + #l
--insert #tbl(keys) values(#m)
SELECT #slen = CASE
WHEN #m = 0 THEN 255
ELSE #m - #l
END
IF #slen > 0
BEGIN
SET #s = Substring(#csv,#l,#slen)
INSERT INTO #tbl
(Keys)
SELECT #s
END
SELECT #l = CASE
WHEN #m = 0 THEN #len + 1
ELSE #m + 1
END
END
RETURN
END
GO
;WITH cte_date
AS
(
SELECT indicatornumerator, REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(indicatornumerator,'(',''),')',''),'[',''),']',''),'SUM',''),'- ','-'),'+ ','') ParsedData
FROM #temped2
)
SELECT *
FROM cte_date
CROSS APPLY dbo.StrParse(' ', ParsedData)
go

How to find duplicate text in a column and remove duplicate in sql server

In the following example
address: AUNDH AUNDH CAMP
I want to remove the duplicate and the result must be
address: AUNDH CAMP
How to perform this in sql server?
You could create this function:
Create FUNCTION dbo.RemoveDuplicate
(
#StringList VARCHAR(MAX),
#Delim CHAR
)
RETURNS
VARCHAR(MAX)
AS
BEGIN
DECLARE #ParsedList TABLE
(
Item VARCHAR(MAX)
)
DECLARE #list1 VARCHAR(MAX), #Pos INT, #rList VARCHAR(MAX)
SET #StringList = LTRIM(RTRIM(#StringList)) + #Delim
SET #pos = CHARINDEX(#delim, #StringList, 1)
WHILE #pos > 0
BEGIN
SET #list1 = LTRIM(RTRIM(LEFT(#StringList, #pos - 1)))
IF #list1 <> ''
INSERT INTO #ParsedList VALUES (CAST(#list1 AS VARCHAR(MAX)))
SET #StringList = SUBSTRING(#StringList, #pos+1, LEN(#StringList))
SET #pos = CHARINDEX(#delim, #StringList, 1)
END
SELECT #rlist = COALESCE(#rlist+#Delim,'') + item
FROM (SELECT DISTINCT Item FROM #ParsedList) t
RETURN #rlist
END
GO
And then use it like this:
Declare #address varchar(300)='AUNDH AUNDH CAMP'
SELECT dbo.RemoveDuplicate(#address,' ') -- The delimiter is an empty space
If you are using SQL Server > 2016 then you can use STRING_SPLIT
Split the column value by space and select distinct column value and then concatenate and create your result column string.
Beware of this kind of processing in productiona databases. There is a lot to think of (is double always wrong, how to treat punctuation marks, are words separated by space only). However, you can use recursion, like in following snippet:
DECLARE #word varchar(MAX) = 'AUNDH AUNDH CAMP';
WITH Splitter AS
(
SELECT 1 N, LEFT(#word,CHARINDEX(' ',#word,1)-1) Word, SUBSTRING(#word, CHARINDEX(' ', #word, 0)+1, LEN(#word)) Rest
UNION ALL
SELECT N+1 N,
CASE WHEN CHARINDEX(' ', Rest, 0)>0 THEN LEFT(Rest, CHARINDEX(' ', Rest, 0)-1) ELSE Rest END,
CASE WHEN CHARINDEX(' ', Rest, 0)>0 THEN SUBSTRING(Rest, CHARINDEX(' ', Rest, 0)+1, LEN(Rest)) ELSE NULL END
FROM Splitter
WHERE LEN(Rest)>0
), Numbered AS
(
SELECT N, Word, ROW_NUMBER() OVER (PARTITION BY Word ORDER BY N) RowNum
FROM Splitter
)
SELECT STUFF((SELECT ' '+Word
FROM Numbered
WHERE RowNum=1
ORDER BY N
FOR XML PATH('')), 1, 1, '') NoDuplicates
You can embed this in a function if you wish.
Using Numbers table:
create table #test
(
id varchar(max)
)
insert into #test
select 'a a b'
union all
select 'c c d'
;with cte
as
(select *,dense_rank() over ( order by id) as rownum
from
#test t
cross apply
(select * from [dbo].[SplitStrings_Numbers](t.id,' '))b
)
,finalresult
as
(select
(
select ''+ item from cte c1 where c1.rownum=c2.rownum
group by item
for xml path('')
)as finalvalue
from cte c2
)
select finalvalue from finalresult
group by finalvalue
This is essentially the same idea as in #TheGameiswar's answer just a bit shorter, unnecessary steps excluded.
create table #test
(
id varchar(max)
)
insert into #test
select 'a a b'
union all
select 'c c d';
select *,
stuff((
select ' '+ item
from [dbo].[DelimitedSplit8K](t.id,' ')
group by item
for xml path('')
),1,1,'') as finalvalue
from #test t
DelimitedSplit8K is a fast string splitter from http://www.sqlservercentral.com/articles/Tally+Table/72993/ . You can use any other one at hand.
DECLARE #source TABLE
(
[str] VARCHAR(MAX)
)
INSERT INTO #source
SELECT 'address: AUNDH AUNDH CAMP'
UNION ALL
SELECT 'address: AUNDH CAMP AUNDH'
UNION ALL
SELECT 'address: BBB AUNDH address:'
UNION ALL
SELECT 'address: BBB AUNDH CAMP'
DECLARE #tbl AS TABLE
(
num INT,
[str] VARCHAR(MAX)
)
DECLARE #result AS TABLE
(
num INT,
n SMALLINT,
[str] VARCHAR(MAX)
)
INSERT INTO #tbl
SELECT ROW_NUMBER() OVER (ORDER BY [str]), [str]
FROM #source
DECLARE
#i INT = 0,
#max_i INT = (SELECT MAX([num]) FROM #tbl),
#str VARCHAR(MAX),
#n SMALLINT
WHILE(#i < #max_i)
BEGIN
SET #str = (SELECT [str] FROM #tbl WHERE [num] = #i + 1)
SET #n = 1
WHILE(CHARINDEX(' ', #str) <> 0)
BEGIN
INSERT INTO #result
SELECT num, #n, SUBSTRING(#str, 1, CHARINDEX(' ', #str) - 1) FROM #tbl WHERE [num] = #i + 1
SET #str = SUBSTRING(#str, CHARINDEX(' ', #str) + 1, LEN(#str))
SET #n += 1
END
INSERT INTO #result
SELECT num, #n, #str FROM #tbl WHERE [num] = #i + 1
SET #i += 1
END
-- result
SELECT SUBSTRING([str], 2, LEN([str])) AS [str]
FROM (
SELECT DISTINCT r_big.num,
(
SELECT ' ' + [str] AS [text()]
FROM #result r_small
WHERE r_small.num = r_big.num
GROUP BY r_small.num, r_small.[str]
ORDER BY num, min(n)
FOR XML PATH('')
) AS [str]
FROM #result r_big
)x

Search all positions of char in string and return as comma separated string

I have string (VARCHAR(255)) that contains only zeros or ones.
I need to search all positions and return them as comma separated string.
I've build two queries using solutions from https://dba.stackexchange.com/questions/41961/how-to-find-all-positions-of-a-string-within-another-string
Here is my code so far:
DECLARE #TERM VARCHAR(5);
SET #TERM = '1';
DECLARE #STRING VARCHAR(255);
SET #STRING = '101011011000000000000000000000000000000000000000';
DECLARE #RESULT VARCHAR(100);
SET #RESULT = '';
SELECT
#RESULT = #RESULT + CAST(X.pos AS VARCHAR(10)) + ','
FROM
( SELECT
pos = Number - LEN(#TERM)
FROM
( SELECT
Number
,Item = LTRIM(RTRIM(SUBSTRING(#STRING, Number, CHARINDEX(#TERM, #STRING + #TERM, Number) - Number)))
FROM
( SELECT ROW_NUMBER () OVER (ORDER BY [object_id]) FROM sys.all_objects
) AS n ( Number )
WHERE
Number > 1
AND Number <= CONVERT(INT, LEN(#STRING))
AND SUBSTRING(#TERM + #STRING, Number, LEN(#TERM)) = #TERM
) AS y
) X;
SELECT
SUBSTRING(#RESULT, 0, LEN(#RESULT));
DECLARE #POS INT;
DECLARE #OLD_POS INT;
DECLARE #POSITIONS VARCHAR(100);
SELECT
#POSITIONS = '';
SELECT
#OLD_POS = 0;
SELECT
#POS = PATINDEX('%1%', #STRING);
WHILE #POS > 0
AND #OLD_POS <> #POS
BEGIN
SELECT
#POSITIONS = #POSITIONS + CAST(#POS AS VARCHAR(2)) + ',';
SELECT
#OLD_POS = #POS;
SELECT
#POS = PATINDEX('%1%', SUBSTRING(#STRING, #POS + 1, LEN(#STRING))) + #POS;
END;
SELECT
LEFT(#POSITIONS, LEN(#POSITIONS) - 1);
I'm wondering if this can be done faster/better? I'm searching only for single character positions and I have only two characters that can occur in my string (0 and 1).
I've build two functions using this code, and run them for 1000 records and got same results in same time, so I can't tell which one is better.
for single record second part gives CPU and reads equals to 0 in Profiler, where first piece of code give me CPU=16 and reads=17.
I need to get result that looks like this: 1,3,5,6,8,9 (when multiple occurrences), 3 for single occurence, NONE if there are no ones.
Some tally table and xml solution:
DECLARE #STRING NVARCHAR(100) = '101011011000000000000000000000000000000000000000';
;with cte as(select ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) p
from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t1(n) cross join
(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t2(n) cross join
(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t3(n))
SELECT STUFF((SELECT ',' + CAST(p AS VARCHAR(100))
FROM cte
WHERE p <= LEN(#STRING) AND SUBSTRING(#STRING, p, 1) = '1'
FOR XML PATH('')), 1, 1, '')
You just generate numbers from 1 to 1000(add more joins if length of string is bigger) and with substring function filter needed values. Then standard trick for concatenating rows to comma separated value.
For old versions:
;with cte as(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) p
FROM sys.all_columns a CROSS JOIN sys.all_columns b)
SELECT STUFF((SELECT ',' + CAST(p AS VARCHAR(100))
FROM cte
WHERE p <= LEN(#STRING) AND SUBSTRING(#STRING, p, 1) = '1'
FOR XML PATH('')), 1, 1, '')
Here is a good article on generating ranges http://dwaincsql.com/2014/03/27/tally-tables-in-t-sql/
EDIT:
;with cte as(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) p
FROM (SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t1 CROSS JOIN
(SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t2 CROSS JOIN
(SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t3 CROSS JOIN
(SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t4 CROSS JOIN
(SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t5 CROSS JOIN
(SELECT 1 AS rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) t6)
Giorgi's response is very clever, but I'd prefer a more old-fashioned approach that's more readable. My suggestion, including test cases:
if object_id('UFN_CSVPOSITIONS') is not null
begin
drop function ufn_csvpositions;
end
go
create function dbo.UFN_CSVPOSITIONS
(
#string nvarchar(255)
,#delimiter nvarchar(1) = ','
)
returns nvarchar(255)
as
begin
--given a string that contains ones,
--return a comma-delimited list of the positions of those ones
--example: '1001' returns '1,4'
declare #result nvarchar(255) = '';
declare #i int = 1;
declare #slen int = len(#string);
declare #idx int = 0;
while #i < #slen
begin
set #idx = charindex('1',#string,#i);
if 0 = #idx
begin
set #i = #slen; --no more to be found, break out early
end
else
begin
set #result = #result + #delimiter + convert(nvarchar(3),#idx);
set #i = #idx; --jump ahead
end;
set #i = #i + 1;
end --while
if (0 < len(#result)) and (',' = substring(#result,1,1))
begin
set #result = substring(#result,2,len(#result)-1)
end
return #result;
end
go
--test cases
DECLARE #STRING NVARCHAR(255) = '';
set #string = '101011011000000000000000000000000000000000000000';
print dbo.UFN_CSVPOSITIONS(#string,',');
set #string = null;
print dbo.UFN_CSVPOSITIONS(#string,',');
set #string = '';
print dbo.UFN_CSVPOSITIONS(#string,',');
set #string = '1111111111111111111111111111111111111111111111111';
print dbo.UFN_CSVPOSITIONS(#string,',');
set #string = '0000000000000000000000000000000000000000000000000';
print dbo.UFN_CSVPOSITIONS(#string,',');
--lets try a very large # of test cases, see how fast it comes out
--255 "ones" should be the worst case scenario for performance, so lets run through 50k of those.
--on my laptop, here are test case results:
--all 1s : 13 seconds
--all 0s : 7 seconds
--all nulls: 1 second
declare #testinput nvarchar(255) = replicate('1',255);
declare #iterations int = 50000;
declare #i int = 0;
while #i < #iterations
begin
print dbo.ufn_csvpositions(#testinput,',');
set #i = #i + 1;
end;
--repeat the test using the CTE method.
--the same test cases are as follows on my local:
--all 1s : 18 seconds
--all 0s : 15 seconds
--all NULLs: 1 second
set nocount on;
set #i = 0;
set #iterations = 50000;
declare #result nvarchar(255) = '';
set #testinput = replicate('1',255);
while #i < #iterations
begin
;with cte as(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) p
FROM sys.all_columns a CROSS JOIN sys.all_columns b)
SELECT #result = STUFF((SELECT ',' + CAST(p AS VARCHAR(100))
FROM cte
WHERE p <= LEN(#testinput) AND SUBSTRING(#testinput, p, 1) = '1'
FOR XML PATH('')), 1, 1, '')
print #result;
set #i = #i + 1;
end;

Reverse only numerical parts of string in sql server

With T-SQL, I'm trying to find the easiest way to reverse numbers in string. so for string like Test123Hello have Test321Hello.
[Before] [After]
Test123Hello Test321Hello
Tt143 Hello Tt341 Hello
12Hll 21Hll
Tt123H3451end Tt321H1543end
you can use this function
CREATE FUNCTION [dbo].[fn_ReverseDigit_MA]
(
#Str_IN nVARCHAR(max)
)
RETURNS NVARCHAR(max)
AS
BEGIN
DECLARE #lenstr AS INT =LEN(#Str_IN)
DECLARE #lastdigend AS INT=0
while (#lastdigend<#lenstr)
BEGIN
DECLARE #strPart1 AS NVARCHAR(MAX)=LEFT(#Str_IN,#lastdigend)
declare #lenstrPart1 AS INT=LEN(#strPart1)
DECLARE #strPart2 AS NVARCHAR(MAX)=RIGHT(#Str_IN,#lenstr-#lastdigend)
declare #digidx as int=patindex(N'%[0-9]%' ,#strPart2)+#lenstrPart1
IF(#digidx=#lenstrPart1)
BEGIN
BREAK;
END
DECLARE #strStartdig AS NVARCHAR(MAX) = RIGHT(#Str_IN,#lenstr-#digidx+1)
declare #NDidx as int=patindex(N'%[^0-9]%' ,#strStartdig)+#digidx-1
IF(#NDidx<=#digidx)
BEGIN
SET #NDidx=#lenstr+1
END
DECLARE #strRet AS NVARCHAR(MAX)=LEFT(#Str_IN,#digidx-1) +REVERSE(SUBSTRING(#Str_IN,#digidx,#NDidx-#digidx)) +RIGHT(#Str_IN,#lenstr-#NDidx+1)
SET #Str_IN=#strRet
SET #lastdigend=#NDidx-1
END
return #Str_IN
END
Just make use of PATINDEX for searching, append to the result string part by part:
CREATE FUNCTION [dbo].[fn_ReverseDigits]
(
#Value nvarchar(max)
)
RETURNS NVARCHAR(max)
AS
BEGIN
IF #Value IS NULL
RETURN NULL
DECLARE
#TextIndex int = PATINDEX('%[^0-9]%', #Value),
#NumIndex int = PATINDEX('%[0-9]%', #Value),
#ResultValue nvarchar(max) = ''
WHILE LEN(#ResultValue) < LEN(#Value)
BEGIN
-- Set the index to end of the string if the index is 0
SELECT #TextIndex = CASE WHEN #TextIndex = 0 THEN LEN(#Value) + 1 ELSE LEN(#ResultValue) + #TextIndex END
SELECT #NumIndex = CASE WHEN #NumIndex = 0 THEN LEN(#Value) + 1 ELSE LEN(#ResultValue) + #NumIndex END
IF #NumIndex < #TextIndex
SELECT #ResultValue = #ResultValue + REVERSE(SUBSTRING(#Value, #NumIndex, #TextIndex -#NumIndex))
ELSE
SELECT #ResultValue = #ResultValue + (SUBSTRING(#Value, #TextIndex, #NumIndex - #TextIndex))
-- Update index variables
SELECT
#TextIndex = PATINDEX('%[^0-9]%', SUBSTRING(#Value, LEN(#ResultValue) + 1, LEN(#Value) - LEN(#ResultValue))),
#NumIndex = PATINDEX('%[0-9]%', SUBSTRING(#Value, LEN(#ResultValue) + 1, LEN(#Value) - LEN(#ResultValue)))
END
RETURN #ResultValue
END
Test SQL
declare #Values table (Value varchar(20))
INSERT #Values VALUES
('Test123Hello'),
('Tt143 Hello'),
('12Hll'),
('Tt123H3451end'),
(''),
(NULL)
SELECT Value, dbo.fn_ReverseDigits(Value) ReversedValue FROM #Values
Result
Value ReversedValue
-------------------- --------------------
Test123Hello Test321Hello
Tt143 Hello Tt341 Hello
12Hll 21Hll
Tt123H3451end Tt321H1543end
NULL NULL
hope this help:
declare #s nvarchar(128) ='Test321Hello'
declare #numStart as int, #numEnd as int
select #numStart =patindex('%[0-9]%',#s)
select #numEnd=len(#s)-patindex('%[0-9]%',REVERSE(#s))
select
SUBSTRING(#s,0,#numstart)+
reverse(SUBSTRING(#s,#numstart,#numend-#numstart+2))+
SUBSTRING(#s,#numend+2,len(#s)-#numend)
Use this function it will handle multiple occurrence of numbers too
create FUNCTION [dbo].[GetReverseNumberFromString] (#String VARCHAR(2000))
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #Count INT
DECLARE #IntNumbers VARCHAR(1000)
declare #returnstring varchar(max)=#String;
SET #Count = 0
SET #IntNumbers = ''
WHILE #Count <= LEN(#String)
BEGIN
IF SUBSTRING(#String, #Count, 1) >= '0'
AND SUBSTRING(#String, #Count, 1) <= '9'
BEGIN
SET #IntNumbers = #IntNumbers + SUBSTRING(#String, #Count, 1)
END
IF (
SUBSTRING(#String, #Count + 1, 1) < '0'
OR SUBSTRING(#String, #Count + 1, 1) > '9'
)
AND SUBSTRING(#String, #Count, 1) >= '0'
AND SUBSTRING(#String, #Count, 1) <= '9'
BEGIN
SET #IntNumbers = #IntNumbers + ','
END
SET #Count = #Count + 1
END
declare #RevStrings table (itemz varchar(50))
INSERT INTO #RevStrings(itemz)
select items from dbo.Split(#IntNumbers,',')
select #returnstring = Replace(#returnstring, itemz,REVERSE(itemz))from #RevStrings
RETURN #returnstring
END
your sample string
select [dbo].[GetReverseNumberFromString]('Tt123H3451end')
result
Tt321H1543end
UPDATE :
if you do not have Split function then first create it
i have included it below
create FUNCTION Split
(
#Input NVARCHAR(MAX),
#Character CHAR(1)
)
RETURNS #Output TABLE (
Items NVARCHAR(1000)
)
AS
BEGIN
DECLARE #StartIndex INT, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output(Items)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
GO
This is a set based approach:
;WITH Tally (n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
), UnpivotCTE AS (
SELECT id, x.c, n, y.isNumber,
n - ROW_NUMBER() OVER (PARTITION BY id, y.isNumber
ORDER BY n) AS grp
FROM mytable
CROSS JOIN Tally
CROSS APPLY (SELECT SUBSTRING(col, n, 1)) AS x(c)
CROSS APPLY (SELECT ISNUMERIC(x.c)) AS y(isNumber)
WHERE n <= LEN(col)
), ToConcatCTE AS (
SELECT id, c, n, isNumber,
grp + MIN(n) OVER (PARTITION BY id, isNumber, grp) AS grpAsc
FROM UnpivotCTE
)
SELECT id, col,
REPLACE(
(SELECT c AS [text()]
FROM ToConcatCTE AS t
WHERE t.id = m.id
ORDER BY id,
grpAsc,
CASE WHEN isNumber = 0 THEN n END,
CASE WHEN isNumber = 1 THEN n END DESC
FOR XML PATH('')), ' ',' ') AS col2
FROM mytable AS m
A tally table is used in order to 'unpivot' all characters of the string. Then ROW_NUMBER is used in order to identify islands of numeric and non-numeric characters. Finally, FOR XML PATH is used to reconstruct the initial string with numerical islands reversed: ORDER BY is used to sort islands of numeric characters in reversed order.
Fiddle Demo here
This would do the specific string you are asking for:
select
substring('Test123Hello',1,4)
+
reverse(substring('Test123Hello',5,3))
+
substring('Test123Hello',8,5)
Judging by the rest of the values it looks like you would need to make templates for any of the alphanumeric patterns you are getting. For example you would apply the above to any values that had the shape:
select * from [B&A] where [before] like '[a-z][a-z][a-z][a-z][0-9][0-9][0-9]
[a-z][a-z][a-z][a-z][a-z]'
In other words, if you put the values (before and after) into a table [B&A] and called the columns 'before' and 'after' then ran this:
select
substring(before,1,4)
+
reverse(substring(before,5,3))
+
substring(before,8,5) as [after]
from [B&A] where [before] like '[a-z][a-z][a-z][a-z][0-9][0-9][0-9][a-z]
[a-z][a-z][a-z][a-z]'
Then it would give you 'Test321Hello'.
However the other 3 rows would not be affected unless you created a similar
'[0-9][a-z]' type template for each alphanumeric shape and applied this to the [B&A] table. You would have to select the results into a temp table or another table.
By applying each template in turn you'd get most of it then you'd have to see how many rows were unaffected and check what the alphanumeric shape is and make more templates. Eventually you have a set of code which, if you ran it would capture all possible combinations.
You could just sit down and design a code in this way which captured all possible combinations of [a-z] and [0-9]. A lot depends on the maximum number of characters you are dealing with.

Retrieve multiple pieces of data from a single SQL Server variable

I have two variables like:
#FieldName
#values
Those two variables hold values like:
#FieldName - contains [a],[b],[c],[d]
#values - contains 5,6,7,8
Now I need to retrieve the data of column 'b' & 'd' only.
How can we get b=6 & d=8?
Thanks in advance.
well I hate to do such a things on SQL Server, but
declare #FieldName nvarchar(max) = '[a],[b],[c],[d]'
declare #values nvarchar(max) = '5,6,7,8'
declare #i int, #j int, #break int
declare #a nvarchar(max), #b nvarchar(max), #result nvarchar(max)
select #break = 0
while #break = 0
begin
select #i = charindex(',', #FieldName), #j = charindex(',', #values)
if #i > 0 and #j > 0
begin
select #a = left(#FieldName, #i - 1), #b = left(#values, #j - 1)
select #FieldName = right(#FieldName, len(#FieldName) - #i), #values = right(#values, len(#values) - #j)
end
else
begin
select #a = #FieldName, #b = #values, #break = 1
end
if #a in ('[b]', '[d]')
select #result = isnull(#result + ' & ', '') + #a + '=' + #b
end
select #result
You can also put all this list into temporary/variable table and do join.
select *
from
(
select T.<yourcolumn>, row_number() over (order by T.<yourcolumn>) as rownum
from <temptable1> as T
) as F
inner join
(
select T.<yourcolumn>, row_number() over (order by T.<yourcolumn>) as rownum
from <temptable2> as T
) as V on V.rownum = F.rownum
Or, even better, you can pass parameters into sp in xml form and not in distinct lists
Try this :
Using XML i'm are trying to spilt the values and storing the result in a table variable
DECLARE #FieldName VARCHAR(MAX),
#values varchar(max)
SET #FieldName = 'a,b,c,d';
SET #values = '5,6,7,8'
SET #FieldName = #FieldName + ',';
SET #values = #values + ',';
DECLARE #X XML
SET #X = CAST('<Item>' + REPLACE(#FieldName, ',', '</Item><Item>') + '</Item>' AS XML)
Declare #X1 XML
Set #X1=CAST('<Position>' + REPLACE(#values, ',', '</Position><Position>') + '</Position>' AS XML)
Declare #FieldSample table
(
FieldName char(1),
rowNum int
)
Declare #valueSample table
(position int,
rowNum int)
Insert into #FieldSample(rowNum,FieldName)
Select * from (
SELECT row_number() over (order by (select 0)) as rowNum, t.value('.', 'char(1)') as field
FROM #x.nodes('/Item') as x(t)
) as a
where a.field !=''
Insert into #valueSample(rowNum,position)
Select * from (
Select row_number() over (order by (select 0)) as rowNum, k.value('.', 'int') as position
from #X1.nodes('/Position') as x1(k)
) as b
where b.position !=0
Basically the last logic you can change it based on how you intend to get the data
Select a.FieldName,b.position from #FieldSample as a
inner join #valueSample as b
on a.rowNum=b.rowNum
where b.position = 6 or b.position =8