Split a type string NUMBER to list of chars - sql

I have a column which has alphanumeric strings like 123x758v961j.
I need to split this alphanumeric number to chars an have blank space between values.
Example: 123x758v961j =====> 1 2 3 x 7 5 8 v 9 6 1 j
I need a function which returns this solution.

Using NGrams8K to split the string into individual characters and then the "classic" FOR XML PATH and STUFF solution to combine the characters back you can do this:
SELECT V.S,
STUFF((SELECT ' ' + NG.token
FROM dbo.NGrams8k(V.S,1) NG
ORDER BY NG.position
FOR XML PATH(''),TYPE).value('.','varchar(100)'),1,1,'') AS S2 --Use a varchar length that is double(-1) then length of your actual data type here
FROM (VALUES('123x758v961j'))V(S);

Just another way
CREATE FUNCTION dbo.SplitToChars(
#String NVARCHAR(300)
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Result NVARCHAR(300) = '';
WITH CTE AS
(
SELECT 1 N
UNION ALL
SELECT N + 1
FROM CTE
WHERE N < LEN(#String)
)
SELECT #Result = CONCAT(#Result, SUBSTRING(#String, N, 1), N' ')
FROM CTE;
RETURN (RTRIM(#Result));
END;
Then just SELECT dbo.SplitToChars(N'123x758v961j')
Returns:
1 2 3 x 7 5 8 v 9 6 1 j
Live Demo

And one more approach ;-)
DECLARE #str VARCHAR(100)='123x758v961j';
WITH Tally(Nmbr) AS
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values
)
SELECT TRIM(
(
SELECT ' ' + SUBSTRING(#str,Nmbr,1)
FROM Tally
ORDER BY Nmbr
FOR XML PATH('')
)
);
The idea is, to use a tally-on-the-fly (a list of running numbers for each position within #str) to read the characters one-by-one. This derived table is reconcatenated.
And - just for fun - to demonstrate the range of approaches, one more using a quirky update (something to avoid actually ;-) )
WITH Tally(Nmbr) AS
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values
)
SELECT #str=STUFF(#str,Nmbr,0,' ')
FROM Tally
ORDER BY Nmbr DESC;
SELECT #str;

Related

How to replace anything between 2 specific characters in SQL Server

I'm trying to replace anything between 2 specific characters in a string that contains multiples of those 2 caracters. Take it as a csv format.
Here an example of what i got as data in that field:
0001, ABCD1234;0002, EFGH432562;0003, IJKL1345hsth;...
What I need to retreive from it is all parts before the ',' but not what are between ',' and ';'
I tried with those formula but no success
SELECT REPLACE(fieldname, ',[A-Z];', ' ') FROM ...
or
SELECT REPLACE(fieldname, ',*;', ' ') FROM ...
I need to get
0001 0002 0003
Is there a way to achieve that?
You can CROSS APPLY to a STRING_SPLIT that uses STRING_AGG (since Sql Server 2017) to stick the numbers back together.
select id, codes
from your_table
cross apply (
select string_agg(left(value, patindex('%_,%', value)), ' ') as codes
from string_split(fieldname, ';') s
where value like '%_,%'
) ca;
GO
id
codes
1
0001 0002 0003
Demo on db<>fiddle here
Extra
Here is a version that also works in Sql Server 2014.
Inspired by the research from #AaronBertrand
The UDF uses a recursive CTE to split the string.
And the FOR XML trick is used to stick the numbers back together.
CREATE FUNCTION dbo.fnString_Split
(
#str nvarchar(4000),
#delim nchar(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH RCTE AS (
SELECT
1 AS ordinal
, ISNULL(NULLIF(CHARINDEX(#delim, #str),0), LEN(#str)) AS pos
, LEFT(#str, ISNULL(NULLIF(CHARINDEX(#delim, #str),0)-1, LEN(#str))) AS value
UNION ALL
SELECT
ordinal+1
, ISNULL(NULLIF(CHARINDEX(#delim, #str, pos+1), 0), LEN(#str))
, SUBSTRING(#str, pos+1, ISNULL(NULLIF(CHARINDEX(#delim, #str, pos+1),0)-pos-1, LEN(#str)-pos ))
FROM RCTE
WHERE pos < LEN(#str)
)
SELECT ordinal, value
FROM RCTE
);
SELECT id, codes
FROM your_table
CROSS APPLY (
SELECT RTRIM((
SELECT LEFT(value, PATINDEX('%_,%', value))+' '
FROM dbo.fnString_Split(fieldname, ';') AS spl
WHERE value LIKE '%_,%'
ORDER BY ordinal
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
) AS codes
) ca
OPTION (MAXRECURSION 250);
id
codes
1
0001 0002 0003
Demo on db<>fiddle here
Alternative version of the UDF (no recursion)
CREATE FUNCTION dbo.fnString_Split
(
#str NVARCHAR(4000),
#delim NCHAR(1)
)
RETURNS #tbl TABLE (ordinal INT, value NVARCHAR(4000))
WITH SCHEMABINDING
AS
BEGIN
DECLARE #value NVARCHAR(4000)
, #pos INT = 0
, #ordinal INT = 0;
WHILE (LEN(#str) > 0)
BEGIN
SET #ordinal += 1;
SET #pos = ISNULL(NULLIF(CHARINDEX(#delim, #str),0), LEN(#str)+1);
SET #value = LEFT(#str, #pos-1);
SET #str = SUBSTRING(#str, #pos+1, LEN(#str));
INSERT INTO #tbl (ordinal, value)
VALUES (#ordinal, #value);
END;
RETURN;
END;
If you're on SQL Server 2017 and don't need a guarantee that the order will be maintained, then LukStorms' answer is perfectly adequate.
However, if you:
care about an order guarantee; or,
are on an older version than 2017 (and can't use STRING_AGG); or,
are on an even older version than 2016 or are in an older compatibility level (and can't use STRING_SPLIT):
Here's an ordered split function that can help (it's long and ugly but you only have to create it once):
CREATE FUNCTION dbo.SplitOrdered
(
#list nvarchar(max),
#delim nvarchar(10)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH w(n) AS (SELECT 0 FROM (VALUES (0),(0),(0),(0)) w(n)),
k(n) AS (SELECT 0 FROM w a, w b),
r(n) AS (SELECT 0 FROM k a, k b, k c, k d, k e, k f, k g, k h),
p(n) AS (SELECT TOP (COALESCE(LEN(#list), 0))
ROW_NUMBER() OVER (ORDER BY ##SPID) -1 FROM r),
spots(p) AS
(
SELECT n FROM p
WHERE (SUBSTRING(#list, n, LEN(#delim + 'x') - 1) LIKE #delim OR n = 0)
),
parts(p,val) AS
(
SELECT p, SUBSTRING(#list, p + LEN(#delim + 'x') - 1,
LEAD(p, 1, 2147483647) OVER (ORDER BY p) - p - LEN(#delim))
FROM spots AS s
)
SELECT listpos = ROW_NUMBER() OVER (ORDER BY p),
Item = LTRIM(RTRIM(val))
FROM parts
);
Then the query can become:
;WITH x AS
(
SELECT id, listpos,
codes = LEFT(Item, COALESCE(NULLIF(CHARINDEX(',', Item),0),1)-1)
FROM dbo.your_table
CROSS APPLY dbo.SplitOrdered(fieldname, ';') AS c
)
SELECT id, codes = (
(SELECT x2.codes + ' '
FROM x AS x2
WHERE x2.id = x.id
ORDER BY x2.listpos
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
)
FROM x GROUP BY id;
Example borrowing from LukStorms' db<>fiddle
Note that, in addition to guaranteeing order and being backward compatible (well, only back so many versions), it also ignores garbage data, e.g. try:
0001, ABCD1234;0002 but no comma

Generate a comma-separated list of numbers in a single string

Is there a way to generate a comma-separated string of a series of numbers where the "begin" and "end" numbers are provided?
For example, provide the numbers 1 and 10 and the output would be a single value of: 1,2,3,4,5,6,7,8,9,10
10/10/2019 edit explaining why I'm interested in this:
My workplace writes queries with several columns in the SELECT statement plus aggregate functions. Then a GROUP BY clause using the column numbers. I figured using a macro that creates a comma-separated list to copy/paste in would save some time.
SELECT t.colA
, t.colB
, t.colC
, t.colD
, t.colE
, t.colF
, t.colG
, t.colH
, t.colI
, t.colJ
, sum(t.colK) as sumK
, sum(t.colL) as sumL
, sum(t.colM) as sumM
FROM t
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
;
You can use a recursive CTE to generate your numbers, and xml_agg to generate your string:
with recursive nums (counter) as
( select * from (select cast(1 as bigint) as counter) t
union all
select
counter + 1
from nums
where counter between 1 and 9
)
select
trim(trailing ',' from cast(xmlagg(cast(counter as varchar(2)) || ',' order by counter) as varchar(100)))
from nums
Check these methods in SQL Server-
IF OBJECT_ID('TEMPDB..#Sample') IS NOT NULL
DROP TABLE #Sample
Create table #Sample
(
NUM int
)
declare #n int
select #n=10
insert into #Sample(NUM)
SELECT NUM FROM (select row_number() over (order by (select null)) AS NUM from sys.columns) A WHERE NUM<=#N
--Method 1 (For SQL SERVER -NEW VERSION Support)
SELECT STRING_AGG(NUM,',') AS EXPECTED_RESULT FROM #Sample
--Method 1 (For SQL SERVER -OLD VERSION Support)
select DISTINCT STUFF(CAST((
SELECT ' ,' +CAST(c.num AS VARCHAR(MAX))
FROM (
SELECT num
FROM #Sample
) c
FOR XML PATH(''), TYPE) AS VARCHAR(MAX)), 1, 2, '') AS EXPECTED_RESULT
from #Sample t
While loop seems appropriate
declare #begin int=1
declare #end int=11
declare #list varchar(500)
if #begin > #end
begin
select 'error, beginning number ' + convert(varchar(500),#begin)
+ ' must not be greater than ending number '
+ convert(varchar(500),#end) + '.' err
return
end
else
set #list = convert(varchar(500),#begin)
;
while #begin < #end
begin
set #begin += 1
set #list = #list + ',' + convert(varchar(500),#begin)
end
select #list
You might want to use varchar(5000) or something depending on how big you want it to get.
disclaimer -- I don't know if this works with teradata
I'm not sure there is a good direct way to generate a series in Teradata. You can fake it a few different ways though. Here's a comma separated list of numbers from 5 to 15, for example:
SELECT TRIM(TRAILING ',' FROM (XMLAGG(TRIM(rn)|| ',' ) (VARCHAR(10000))))
FROM (SELECT 4 + ROW_NUMBER() OVER (ORDER BY Sys_Calendar."CALENDAR".day_of_calendar) as rn FROM Sys_Calendar."CALENDAR" QUALIFY rn <= 15) t
I've only used sys_calendar.calendar here because it's a big table. Any big table would do here though.
Here's one way to do it in Teradata:
SELECT ARRAY_AGG(src.RowNum)
FROM (
SELECT ROW_NUMBER() OVER() AS RowNum
FROM sys_calendar.calendar
QUALIFY RowNum BETWEEN <begin_num> AND <end_num>
) src
This will give you the output as an ARRAY data type, which you can probably cast as a VARCHAR. It also assumes begin_num > 0 and <end_num> is less than the number of rows in the sys_calendar.calendar view. You can always fiddle with this to fit your required range of values.
There are also DelimitedBuild UDFs out there (if you can find one) that can be used to convert row values into delimited strings.
The cheapest way to achieve your goal is this one (no functions, or joins to tables required):
WITH RECURSIVE NumberRanges(TheNumber,TheString) AS
(
SELECT 1 AS TheNumber,casT(1 as VARCHAR(500)) as TheString
FROM
(
SELECT * FROM (SELECT NULL AS X) X
) DUMMYTABLE
UNION ALL
SELECT
TheNumber + 1 AS TheNumber,
TheString ||',' || TRIM(TheNumber+1)
FROM NumberRanges
WHERE
TheNumber < 10
)
SELECT TheString
FROM NumberRanges
QUALIFY ROW_NUMBER() OVER ( ORDER BY TheNumber DESC) = 1;
Result String: 1,2,3,4,5,6,7,8,9,10

SQL Server: Split string value with single quotations

Trying to modify a set of code, so that given a string, the string has to be split and passed to the code to be used.
This is the code that I have right now.
DECLARE #xml xml,
#str varchar(100),
#delimiter varchar(10)
SET #str = '100'
SET #delimiter = ','
SET #xml = cast(('<X>'+replace(#str, #delimiter, '</X><X>')+'</X>') as
xml)
SELECT C.value('.', 'varchar(10)') as value
FROM #xml.nodes('X') as X(C)
For a single-valued string, this works just fine. But I need to use more than one like, ('100', '100A', '100B'...).
The string value will not contain anything other than 3-digit numbers or 3-digit numbers + an alphabet character, or 3 alphabet letter characters.
I also tried something else, but this is too slow.
declare #values table
(
Value varchar(1000)
)
insert into #values values ('100'),('100A'),('100B'),('100C')
Select *
from table
where myField in (select value from #value)
How can I modify the code for this requirement?
You need to create a table valued function that you can pass your string to split into using a cross apply:
Function
create function [dbo].[fn_StringSplit4k]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(20) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
-- Start tally table with 10 rows.
with n(n) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(isnull(#str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
--,s(s) as (select 1 union all select t+len(replace(#delimiter,' ','.')) from t where substring(isnull(#str,''),t,len(replace(#delimiter,' ','.'))) = #delimiter)
,s(s) as (select 1 union all select t+1 from t where case when #delimiter = '' and t < len(#str) then 1 else case when substring(isnull(#str,''),t,1) = #delimiter then 1 else 0 end end = 1)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,case when #delimiter = '' then 1 else isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) end from s)
select rn
,item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num
or #num is null;
Usage
select s.item
from YourTable as t
cross apply dbo.fn_StringSplit4k(t.YourString,',',null) as s;

SQL Select Concat between 2 numbers

I work with SQL Server 2012 and need a concatenate between 2 different columns.
eg:
3 and 7 = 34567
or 1 and 4 = 1234
or 2 and 2 = 2
When I use the Concat Function, I am just able to Concate the first and the last number. But I need the numbers between, too.
Try this query. Here firstcolumn =3 and secondcolumn=7
SELECT t.Id,
,STUFF((SELECT '' + CAST( n AS VARCHAR(50)) [text()]
FROM (SELECT DISTINCT n = number
FROM master..[spt_values]
WHERE number >= firstcolumn AND number <= secondcolumn
)a
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,0,'') List_Output
FROM tablename t
There are multiple ways to generate sequences in sql-server. Here is a simple that doesn't need a number-table:
WITH Numbers AS
(
SELECT TOP (2000) n = ROW_NUMBER() OVER (ORDER BY object_id)
FROM sys.all_objects ORDER BY n
)
SELECT n FROM Numbers
WHERE n BETWEEN 3 AND 7
Here's a recursive query that will go from start to end recursively and generate the string you want or an INTEGER value:
DECLARE #start INT = 3
DECLARE #end INT = 7
DECLARE #int_value INT = 0
DECLARE #str_value VARCHAR(100) = '';
WITH rec AS (
SELECT #start AS val
UNION ALL
SELECT val + 1
FROM rec
WHERE val < #end
)
SELECT #str_value = CONCAT(#str_value, val),
#int_value = #int_value * 10 + val
FROM rec
SELECT #str_value, #int_value
This is Itzik's style
declare #values varchar(100)='', #from int, #to int
select #from=3, #to=7
;WITH
n0 AS (SELECT 0 AS number UNION ALL SELECT 0),
n1 AS (SELECT 0 AS number FROM n0 AS a CROSS JOIN n0 AS b),
n2 AS (SELECT 0 AS number FROM n1 AS a CROSS JOIN n1 AS b),
n3 AS (SELECT 0 AS number FROM n2 AS a CROSS JOIN n2 AS b)
select #values=#values+ltrim(sno) from
(select row_number() over (order by number) as sno from n3) as t
where sno between #from and #to
select #values as [values]
Thank you for the Answer.
I am going to use the answer from #Mukesh Kalgude.
So, my full query is the follow:
select
DayFrom,DayTo,
STUFF((SELECT TOP 7'' + CAST( n AS VARCHAR(50)) [text()]
FROM (SELECT DISTINCT n = number
FROM master..[spt_values]
WHERE number >= DayFrom AND DayTo <= 7
)a
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,0,'') List_Output
from SwitchProfilePairs
The result is
dayFrom = 1 day To = 1 But the List_Output is 1234567
Try this using SUBSTRING() function(Fiddle example):
--Declare sample table
DECLARE #T TABLE (id int identity, numCol1 int, numCol2 int)
--Add some values
INSERT #T (numCol1, numCol2)
VALUES (3, 7), (1, 4), (2, 2)
--Actual Query
SELECT *, SUBSTRING('123456789', numCol1, numCol2 - numCol1 + 1) Number
FROM #T
Above query works only with single digit numbers. Modified version (below) to work with numbers like 34, 78
SELECT *,
SUBSTRING('123456789', CONVERT(int, LEFT(numCol1,1)),
CONVERT(int, RIGHT(numCol2, 1)) - convert(int, LEFT(numCol1,1)) + 1) YourNumber
FROM #T
Note: Number column is returning a string, can be converted to an int using convert() function

Get the value of a column replacing the comma separator

How can I get each value of a column that has a comma separator in her value ?
Example:
ID ColumnUnified
1 12,34,56,78
2 80,99,70,56
What I want is a query to get the number without comma. If possible, in collumns.
12 34 56 78
This will work for any number of values http://beyondrelational.com/modules/2/blogs/70/posts/10844/splitting-delimited-data-to-columns-set-based-approach.aspx
The solution Madhivanan's link refers to is very creative, but I had a slight problem with it on SQL Server 2012 related to the name of one of the columns (Start). I've modified the code in his answer to use StartPos instead of Start for the column name.
I was not familiar with the system procedure spt_values, but I found a very informative description of the procedure here on SO for those who are interested in exactly how this solution works.
Finally, here's the (slightly) revised code from Madhivana's answer:
CREATE TABLE #test(id int, data varchar(100))
INSERT INTO #test VALUES (1,'This,is,a,test,string')
INSERT INTO #test VALUES (2,'See,if,it,can,be,split,into,many,columns')
DECLARE #pivot varchar(8000)
DECLARE #select varchar(8000)
SELECT #pivot = COALESCE(#pivot + ',', '') + '[col'
+ CAST(number + 1 AS VARCHAR(10)) + ']'
FROM master..spt_values
WHERE type = 'p'
AND number <= ( SELECT MAX(LEN(data) - LEN(REPLACE(data, ',', '')))
FROM #test
)
SELECT #select = '
select p.*
from (
select
id,substring(data, StartPos+2, endPos-StartPos-2) as token,
''col''+cast(row_number() over(partition by id order by StartPos) as varchar(10)) as n
from (
select
id, data, n as StartPos, charindex('','',data,n+2) endPos
from (select number as n from master..spt_values where type=''p'') num
cross join
(
select
id, '','' + data +'','' as data
from
#test
) m
where n < len(data)-1
and substring(data,n+1,1) = '','') as data
) pvt
Pivot ( max(token)for n in (' + #pivot + '))p'
EXEC(#select)
DROP TABLE #test