SQL How to return column value based in other substring position delimited by commas - sql

My first post! I hope that you help me :)
I'm working in SQL 2017 and I have a table like this:
+----+------------------+------------------+
| ID | Col1 | Col2 |
+-----+------------------+------------------+
| 110 | 450,2,50,110,600 | 3,45,30,901,1001 |
| 250 | 2,250,300,1 | 1,33,540,900 |
| 45 | 1,45,320 | 200,444,600 |
+-----+------------------+------------------+
The logic is to find the ID position in Col1 and return based in that position the substring in Col2.
Example:
ID 110 match 4th position in Col1 so should return 901 value in Col2.
ID 250 match 2nd position in Col1 so should return 33 value in Col2.
ID 45 match 2nd position in Col1 so should return 400 value in Col2.
I made different attempts without any success, probably I'm in wrong direction.
Can you please help with this?
The output that I want is the specific values from Col2.
Thanks!

For SQL Server 2016+ (I'm not going to do one for earlier because of STRING_SPLIT support
DECLARE #BadDesign table (ID int, Col1 varchar(200), Col2 varchar(200));
INSERT #BadDesign VALUES
(110,'450,2,50,110,600', '3,45,30,901,1001'),
(250,'2,250,300,1', '1,33,540,900'),
(45 ,'1,45,320', '200,444,600')
SELECT
*
FROM
#BadDesign B
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), value
FROM
STRING_SPLIT(B.Col1, ',')
) b1
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), value
FROM
STRING_SPLIT(B.Col2, ',')
) b2
WHERE
B.ID = b1.value AND b1.rn = b2.rn
No guarantees on ROW_NUMBER consistency over the output of STRING_SPLIT.
Edit: also requires database compatibility to be 130 or above (SQL Server 2016)
The STRING_SPLIT function is available only under compatibility level
130. If your database compatibility level is lower than 130, SQL Server will not be able to find and execute STRING_SPLIT function. You
can change a compatibility level of database using the following
command: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130

Using a Custom String Split Function (this answer is using one written by Aaron Bertrand), so not restricting the use on SQL2016+
CREATE FUNCTION dbo.SplitStringsOrdered (
#List NVARCHAR(2000)
, #Delimiter NVARCHAR(32)
)
RETURNS TABLE
AS
RETURN (
SELECT
rn = ROW_NUMBER() OVER (ORDER BY Number)
, Item
FROM
(
SELECT
Number
, Item = LTRIM(RTRIM(SUBSTRING(
#List
, Number
, CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number
)
)
)
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM
sys.all_objects
) AS n(Number)
WHERE
Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y
);
GO
And amending the query created by #gbn in his/her answer - is this allowed on SO?
DECLARE #BadDesign table (ID int, Col1 varchar(200), Col2 varchar(200));
INSERT #BadDesign VALUES
(110,'450,2,50,110,600', '3,45,30,901,1001'),
(250,'2,250,300,1', '1,33,540,900'),
(45 ,'1,45,320', '200,444,600')
SELECT
B.*, Col1Value=b1.Item, Cal2Value = B2.Item
FROM
#BadDesign B
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), F.Item
FROM
dbo.SplitStringsOrdered(B.Col1, ',') F
) b1
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), F1.Item
FROM
dbo.SplitStringsOrdered(B.Col2, ',') F1
) b2
WHERE
b1.rn = b2.rn

Related

Joining sql tables with no common columns without ordering

I have my data in a form of 2 coma separated strings
DECLARE #ids nvarchar(max) = '1,2,3'
DECLARE #guids nvarchar(max) =
'0000000-0001-0000-0000-000000000000,
`0000000-0022-0000-0000-000000000000`,
`0000000-0013-0000-0000-000000000000'`
I need them in a table as separate columns based on their position in the string
Table1
| Id | Guid |
| 1 | 0000000-0001-0000-0000-000000000000 |
| 2 | 0000000-0022-0000-0000-000000000000 |
| 3 | 0000000-0013-0000-0000-000000000000 |
I can split both strings into separate tables by using
DECLARE #split_ids
(value nvarchar(max))
DECLARE #xml xml
SET #xml = N'<root><r>' + replace(#ids, ',' ,'</r><r>') + '</r></root>'
INSERT INTO #split_ids(Value)
SELECT r.value('.','nvarchar(max)')
FROM #xml.nodes('//root/r') as records(r)
I've tried
SELECT t1.*, t2.*
FROM (SELECT t1.*, row_number() OVER (ORDER BY [Value]) as seqnum
from cte_Ids t1
) t1 FULL OUTER JOIN
(SELECT t2.*, row_number() OVER (ORDER BY [Value]) as seqnum
from cte_barcodes t2
) t2
ON t1.seqnum = t2.seqnum;
But that orders the tables by Value and my data is random and can't be ordered.
Is there a way of joining tables based on their row numbers without ordering them first?
Or is there another way of inserting data from a string to a table?
You do not need to split and/or insert the input data into separate tables. In this situation you simply need to parse the input strings and get the substrings and their ordinal positions (an XML-based approach or a splitter function are possible solutions).
But if you use SQL Server 2016+, a JSON-based approach is also an option. The idea is to transform the strings into valid JSON arrays (1,2,3 into [1,2,3]), parse the arrays with OPENJSON() and join the tables returned from OPENJSON() calls. As is explained in the documentation, the columns that OPENJSON() function returns (when the default schema is used) are key, value and type and in case of JSON array, the key column holds the index of the element in the specified array.
DECLARE #ids nvarchar(max) = N'1,2,3'
DECLARE #guids nvarchar(max) = N'0000000-0001-0000-0000-000000000000,0000000-0022-0000-0000-000000000000,0000000-0013-0000-0000-000000000000'
SELECT j1.[value] AS Id, j2.[value] AS Guid
FROM OPENJSON(CONCAT('[', #ids, ']')) j1
JOIN OPENJSON(CONCAT('["', REPLACE(#guids, ',', '","'), '"]')) j2 ON j1.[key] = j2.[key]
Result:
Id Guid
1 0000000-0001-0000-0000-000000000000
2 0000000-0022-0000-0000-000000000000
3 0000000-0013-0000-0000-000000000000
You need row numbering over initial order, this means that you should use some constant expression in window function order_by clause.
SQL server does not allow use constants directly, but over(order_by (select 1)) is allowed:
SELECT t1.*, t2.*
FROM (SELECT t1.*, row_number() OVER (ORDER BY (select 1)) as seqnum
from cte_Ids t1
) t1 FULL OUTER JOIN
(SELECT t2.*, row_number() OVER (ORDER BY (select 1)) as seqnum
from cte_barcodes t2
) t2
ON t1.seqnum = t2.seqnum;
Note that this doesn't guarantee initial order (it will be unspecified), but often it behaves correctly :)
One of solutions is to parse your comma separated values in a loop (using WHILE) from both variables. Then you could insert those extracted in the same iteration values at once as one row to a table.
One solution uses recursive CTEs:
with cte as (
select cast(null as nvarchar(max)) as id, cast(null as nvarchar(max)) as guid, #ids + ',' as rest_ids, #guids + ',' as rest_guids, 0 as lev
union all
select left(rest_ids, charindex(',', rest_ids) - 1),
left(rest_guids, charindex(',', rest_guids) - 1),
stuff(rest_ids, 1, charindex(',', rest_ids), ''),
stuff(rest_guids, 1, charindex(',', rest_guids), ''),
lev + 1
from cte
where rest_ids <> ''
)
select id, guid
from cte
where lev > 0;
Here is a db<>fiddle.

Count number of repeated character in a given string

How do I count the number of occurrences of repeated $ character in the given strings.
For ex:
String = '$$$$ABC$$$DE$$$' --> Answer is 4,3,3
String = '###$$%%ANE$$$$$' --> Answer is 2,5
I have no idea how to do it so did not do any attempts.
Thanks for your help.
For Reproducing:
DDL and Inserts:
Create table xyz(text varchar(200));
Insert into xyz values('$$$$ABC$$$DE$$$');
Insert into xyz values('###$$%%ANE$$$$$');
What I need to do: Count the repeated number of '$'
Desired output, based on the sample data in #1 above.
text = '$$$$ABC$$$DE$$$' --> Answer is 4,3,3
text = '###$$%%ANE$$$$$' --> Answer is 2,5
SQL Server version: Microsoft SQL Server 2019 (RTM) - 15.0.2000.5
Please try the following solution. It will work starting from SQL Server 2017 onwards.
It is based on use of the TRANSLATE() function, and XML and XQuery.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(30));
INSERT INTO #tbl (tokens) VALUES
('$$$$ABC$$$DE$$$'), --> Answer is 4,3,3
('###$$%%ANE$$$$$'); --> Answer is 2,5
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
;WITH cte AS
(
SELECT *
, REPLACE(TRANSLATE(tokens, '$', SPACE(1)),' ','') AS JunkCharacters
FROM #tbl
)
SELECT *
, REPLACE(TRY_CAST('<root><r><![CDATA[' +
REPLACE(TRANSLATE(tokens, TRIM(JunkCharacters), SPACE(LEN(TRIM(JunkCharacters)))), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
.query('
for $x in /root/r[text()]
return data(string-length($x))
').value('.', 'VARCHAR(20)'), SPACE(1), ',') AS CleansedTokensCounter
FROM cte;
Output
+----+-----------------+----------------+-----------------------+
| ID | tokens | JunkCharacters | CleansedTokensCounter |
+----+-----------------+----------------+-----------------------+
| 1 | $$$$ABC$$$DE$$$ | ABCDE | 4,3,3 |
| 2 | ###$$%%ANE$$$$$ | ###%%ANE | 2,5 |
+----+-----------------+----------------+-----------------------+
We can do this with a number of steps:
We use a tally/numbers table to shred the string into individual characters. The tally is calculated on the fly with a couple of cross-joins and ROW_NUMBER
We then calculate a grouping ID for each group of characters, using a standard gaps-and-islands technique: a windowed sum of each starting row
Filter down to the character we want, group it by ID and return a count of rows in each group.
This returns a new row for every group of $ characters
Create table xyz(text varchar(200));
Insert into xyz values('$$$$ABC$$$DE$$$');
Insert into xyz values('###$$%%ANE$$$$$');
WITH
L0 AS ( SELECT 1 AS c
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
-- you can allow for larger strings with more cross-joins
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L1 )
SELECT
xyz.[text],
r.numRepetitions
FROM xyz
CROSS APPLY (
SELECT numRepetitions = COUNT(*)
FROM (
SELECT TOP(LEN(xyz.[text]))
thisChar = SUBSTRING(xyz.[text], rownum, 1),
groupId = SUM(CASE WHEN rownum = 1 OR SUBSTRING(xyz.[text], rownum, 1) <> SUBSTRING(xyz.[text], rownum - 1, 1) THEN 1 ELSE 0 END)
OVER (ORDER BY rownum ROWS UNBOUNDED PRECEDING)
FROM Nums
ORDER BY rownum
) AS chars
WHERE thisChar = '$'
GROUP BY groupId
) AS r;
If you want a single comma-separated list of row-counts, you need to subquery again
CROSS APPLY (
SELECT numRepetitions = STRING_AGG(CAST(numRepetitions AS varchar(10)), ',')
FROM (
SELECT numRepetitions = COUNT(*)
FROM (
SELECT TOP(LEN(xyz.[text]))
thisChar = SUBSTRING(xyz.[text], rownum, 1),
groupId = SUM(CASE WHEN rownum = 1 OR SUBSTRING(xyz.[text], rownum, 1) <> SUBSTRING(xyz.[text], rownum - 1, 1) THEN 1 ELSE 0 END)
OVER (ORDER BY rownum ROWS UNBOUNDED PRECEDING)
FROM Nums
ORDER BY rownum
) AS chars
WHERE thisChar = '$'
GROUP BY groupId
) AS groups
) AS r;

SQL Server Loop thru rows to form Groups

I using SQL Server 2008 R2 / 2014. I wish to find a SQL query that can do the following:
Rules:
Each [Group] must have [Number] 1 to 6 to be complete group.
[Name] in each [Group] must be unique.
Each row only can use 1 time.
Table before sorting is...
Name Number Group
---- ------ -----
A 1
B 6
A 123
C 3
B 4
C 23
D 45
D 4
C 56
A 12
D 56
After sorting, result I want is below or similar....
Name Number Group
---- ------ -----
A 1 1
C 23 1
D 45 1
B 6 1
A 123 2
D 4 2
C 56 2
A 12 3
C 3 3
B 4 3
D 56 3
What I tried before is to find a subgroup that have [Number] consist of 1-6 with below concatenate method...
SELECT *
FROM [Table1] ST2
WHERE
SUBSTRING((SELECT ST1.[Number] AS [text()]
FROM [Table1] ST1
-- WHERE ST1.[Group] = ST2.[Group]
ORDER BY LEFT(ST1.[Number],1)
FOR XML PATH ('')), 1, 1000) = '123456'
Maybe you should check ROW_NUMBER function.
select Name
, Number
, ROW_NUMBER () OVER(PARTITION BY Name ORDER BY Number) as Group
from [Table1]
If you have more than 6 rows with same NAME value then it will return more groups. You can filter additional groups out since you are interested in only 6 groups with unique values of NAME column.
I'm not sure if this can be done more simply or not, but here's my go at it...
Advanced warning, this requires some means of splitting strings. Since you're not on 2016, I've included a function at the beginning of the script.
The bulk of the work is a recursive CTE that builds the Name and Number columns into comma delimited groups. We then reduce our working set to only the groups where the numbers would create 123456, split the groups and use ROW_NUMBER() OVER... to identify them, and then select based on the new data.
Demo: http://rextester.com/NEXG53500
CREATE FUNCTION [dbo].[SplitStrings]
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
CREATE TABLE #temp
(
name VARCHAR(MAX),
number INT
)
INSERT INTO #temp
VALUES
('a',1),
('b',6),
('a',123),
('c',3),
('b',4),
('c',23),
('d',45),
('d',4),
('c',56),
('a',12),
('d',56);
/*** Recursively build groups based on information from #temp ***/
WITH groupFinder AS
(
SELECT CAST(name AS VARCHAR(MAX)) AS [groupNames], CAST(number AS VARCHAR(max)) AS [groupNumbers] FROM #temp
UNION ALL
SELECT
cast(CONCAT(t.[Name],',',g.[groupNames]) as VARCHAR(MAX)),
CAST(CONCAT(CAST(t.[Number] AS VARCHAR(max)),',',CAST(g.[groupNumbers] AS VARCHAR(max))) AS VARCHAR(max))
FROM #temp t
JOIN groupFinder g
ON
g.groupNames NOT LIKE '%' + t.name+'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/100 AS VARCHAR(10)) +'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/10 AS VARCHAR(10)) +'%'
AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number%10 AS VARCHAR(10)) +'%'
)
/*** only get groups where the numbers form 123456 ***/
, groupPruner AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [groupNames]) AS [rn] FROM groupFinder WHERE REPLACE([groupNumbers],',','') = '123456'
)
/*** split the name group and give it identifiers ***/
, nameIdentifier AS
(
SELECT g.*, c1.[item] AS [Name], ROW_NUMBER() OVER (PARTITION BY [rn] ORDER BY (SELECT NULL)) AS [rn1]
FROM groupPruner g
CROSS APPLY splitstrings(g.groupnames,',') c1
)
/*** split the number group and give it identifiers ***/
, numberIdentifier AS
(
SELECT g.*, c1.[item] AS [Number], ROW_NUMBER() OVER (PARTITION BY [rn], [rn1] ORDER BY (SELECT NULL)) AS [rn2]
FROM nameIdentifier g
CROSS APPLY splitstrings(g.groupNumbers,',') c1
)
SELECT [Name], [Number], [rn] AS [Group]
--,groupnames, groupNumbers /*uncomment this line to see the groups that were built*/
FROM numberIdentifier
WHERE rn1 = rn2
ORDER BY rn, rn1
DROP TABLE #temp

Split SQL server string that has Decimal points into multiple Columns

I have the following table
Col
=========================
1270.8/847.2/254.16/106.9
And I would like to be split into columns like so:
Col1 Col2 Col3 Col4
============================================
1270.8 847.2 254.16 106.9
I have the code below, but it doesn't take the decimal into consideration.
Declare #Sample Table
(MachineName varchar(max))
Insert into #Sample
values ('1270.8/847.2/254.16');
SELECT
Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 1)) As [M1]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 2)) As [M2]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 3)) As [M3]
FROM #Sample
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden and conditional aggregation:
declare #Sample Table (id int not null identity(1,1), MachineName varchar(max));
insert into #Sample values ('1270.8/847.2/254.16'),('1270.8/847.2/254.16/106.9');
select
t.id
, m1 = max(case when s.ItemNumber = 1 then s.Item end)
, m2 = max(case when s.ItemNumber = 2 then s.Item end)
, m3 = max(case when s.ItemNumber = 3 then s.Item end)
, m4 = max(case when s.ItemNumber = 4 then s.Item end)
from #Sample t
cross apply dbo.delimitedsplit8K(MachineName,'/') s
group by id
rextester demo: http://rextester.com/WJVLB77682
returns:
+----+--------+-------+--------+-------+
| id | m1 | m2 | m3 | m4 |
+----+--------+-------+--------+-------+
| 1 | 1270.8 | 847.2 | 254.16 | NULL |
| 2 | 1270.8 | 847.2 | 254.16 | 106.9 |
+----+--------+-------+--------+-------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Everyone should have a good split/parse function as illustrated by SQLZim (+1), but another option could be as follow:
Declare #YourTable table (ID int,Col varchar(max))
Insert Into #YourTable values
(1,'1270.8/847.2/254.16/106.9')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = xDim.value('/x[1]','float')
,Col2 = xDim.value('/x[2]','float')
,Col3 = xDim.value('/x[3]','float')
,Col4 = xDim.value('/x[4]','float')
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
ID Col1 Col2 Col3 Col4
1 1270.8 847.2 254.16 106.9
EDIT - If 2012+, and just to be super-duper safe
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = try_convert(float,xDim.value('/x[1]','varchar(100)'))
,Col2 = try_convert(float,xDim.value('/x[2]','varchar(100)'))
,Col3 = try_convert(float,xDim.value('/x[3]','varchar(100)'))
,Col4 = try_convert(float,xDim.value('/x[4]','varchar(100)'))
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
If you are using SQL Server 2016, you can use String_Split()
;with cte as (
select RowN = row_number() over(order by (SELECT NULL)), * from string_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
If you are using less than SQL Server 2016 version then you might require to use custom split functions... Many ways to write custom split function one easier way is to write using xml
CREATE Function dbo.udf_split( #str varchar(max), #delimiter as varchar(5) )
RETURNS #retTable Table
( RowN int,
value varchar(max)
)
AS
BEGIN
DECLARE #xml as xml
SET #xml = cast(('<X>'+replace(#str,#delimiter ,'</X><X>')+'</X>') as xml)
INSERT INTO #retTable
SELECT RowN = Row_Number() over (order by (SELECT NULL)), N.value('.', 'varchar(MAX)') as value FROM #xml.nodes('X') as T(N)
RETURN
END
--Your query
;with cte as (
select * from udf_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
But mine is similar to John's solution... Just now only looking at that
If you are using in value in a table then you can use cross apply as below
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
select * from #t t cross apply string_split(t.v, '/')
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
--Just to get all the values
select * from #t t cross apply string_split(t.v, '/')
--Inorder to get into same row -pivoting the data
select * from (
select * from #t t cross apply (select RowN=Row_Number() over (Order by (SELECT NULL)), value from string_split(t.v, '/') ) d) src
pivot (max(value) for src.RowN in([1],[2],[3],[4])) p

need to get description of id from another table which are pipe separated

I have two tables table1 and table2
in table1 there is a column with name typeids in which ids are pipe separated
ex: 2|3|4 --> these ids are the primary key in table2
table2 contains Id, Description which has data like
2-text1
3-text2
4-text3
now I need to get the table1 contents but 2|3|4 will be replaced by
text1|text2|text3
This is a really poor design of your database and as others have said you should do your level best to get it changed.
That said, this is possible. It is just ugly as sin and I am sure performs like a dog, but you can blame that on your database designer. In short, you need to split your id string on the | character, join each element to your table2 and then concatenate them all back together using for xml. As you are using SQL Server 2016 you can use STRING_SPLIT instead of the function I have used below, though as I don't currently have access to a 2016 box here we are (Working example):
create function dbo.StringSplit
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(1) = ',' -- 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 n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))
-- Select the same number of rows as characters in isnull(#str,'') as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest isnull(#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+1 from t where substring(isnull(#str,''),t,1) = #delimiter)
-- 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,isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) from s)
select rn as ItemNumber
,Item
from(select row_number() over(order by s) as rn
,substring(isnull(#str,''),s,l) as item
from l
) a
where rn = #num -- Return a specific value where specified,
or #num is null -- Or everything where not.
)
go
declare #t1 table (id varchar(10));
insert into #t1 values
('2|3|4')
,('5|6|7');
declare #t2 table (id varchar(1), description varchar(10));
insert into #t2 values
('2','text1')
,('3','text2')
,('4','text3')
,('5','text4')
,('6','text5')
,('7','text6')
;
select t1.id
,stuff((select '|' + t2.description
from #t1 as t1a
cross apply dbo.StringSplit(t1a.id,'|',null) as s
join #t2 as t2
on s.Item = t2.id
where t1.id = t1a.id
for xml path('')
),1,1,''
) as t
from #t1 as t1;
Output:
+-------+-------------------+
| id | t |
+-------+-------------------+
| 2|3|4 | text1|text2|text3 |
| 5|6|7 | text4|text5|text6 |
+-------+-------------------+