Infinite loop CTE with OPTION (maxrecursion 0) - sql-server

I have CTE query with large record on it. Previously it worked fine. But lately, it throws an error for some members
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
So I put OPTION (maxrecursion 0) or OPTION (maxrecursion 32767) on my query, because I don't want to limit the records. But, the result is the query takes forever to load. How do I solve this?
Here's my code:
with cte as(
-- Anchor member definition
SELECT e.SponsorMemberID , e.MemberID, 1 AS Level
FROM tblMember AS e
where e.memberid = #MemberID
union all
-- Recursive member definition
select child.SponsorMemberID , child.MemberID, Level + 1
from tblMember child
join cte parent
on parent.MemberID = child.SponsorMemberID
)
-- Select the CTE result
Select distinct a.*
from cte a
option (maxrecursion 0)
EDIT: remove unnecessary code to easy understand
SOLVED: So the issue is not came from maxrecursion. It's from the CTE. I don't know why but possibly it contain any sponsor cycles: A -> B -> C -> A -> ... (Thanks to #HABO)
I tried this method and it works. Infinite loop in CTE when parsing self-referencing table

If you are hitting the recursion limit, you either have considerable depth in sponsoring relationships or a loop in the data. A query like the following will detect loops and terminate the recursion:
declare #tblMember as Table ( MemberId Int, SponsorMemberId Int );
insert into #tblMember ( MemberId, SponsorMemberId ) values
( 1, 2 ), ( 2, 3 ), ( 3, 5 ), ( 4, 5 ), ( 5, 1 ), ( 3, 3 );
declare #MemberId as Int = 3;
declare #False as Bit = 0, #True as Bit = 1;
with Children as (
select MemberId, SponsorMemberId,
Convert( VarChar(4096), '>' + Convert( VarChar(10), MemberId ) + '>' ) as Path, #False as Loop
from #tblMember
where MemberId = #MemberId
union all
select Child.MemberId, Child.SponsorMemberId,
Convert( VarChar(4096), Path + Convert( VarChar(10), Child.MemberId ) + '>' ),
case when CharIndex( '>' + Convert( VarChar(10), Child.MemberId ) + '>', Path ) = 0 then #False else #True end
from #tblMember as Child inner join
Children as Parent on Parent.MemberId = Child.SponsorMemberId
where Parent.Loop = 0 )
select *
from Children
option ( MaxRecursion 0 );

Related

T-SQL Query Returning NULL Rows Using APPLY Operator

I'm working on an SQL query which combines information if ServiceStart and ServiceEnd overlap with another entry for that provider. For the most part, the query is working, however, I'm getting a lot of NULL rows back.
I believe this may have something to do with using the OUTER APPLY. However, changing the OUTER APPLY to CROSS APPLY doesn't work either. I've also attempted to specify within the query WHERE ServiceID IS NOT NULL. The results are the same either way. Below I've provided a screen shot of the results currently being returned as well as a link to rextester.com with full code.
DECLARE #StartTime DATETIME, #EndTime DATETIME
SET #StartTime = '2018-11-01'
SET #EndTime = '2018-11-05'
;WITH cte
AS (SELECT c.ClaimID,
c.ProvidedBy,
c.StartTime,
c.EndTime,
oa.ServiceStart,
oa.ServiceEnd,
c.ClaimDetailID,
c.ServiceID,
c.Duration
FROM tbl_Claims c
OUTER APPLY
(
SELECT MIN(t2.StartTime) ServiceStart,
MAX(t2.EndTime) ServiceEnd
FROM tbl_Claims t2
WHERE t2.StartTime <= c.EndTime
AND (t2.EndTime >= c.StartTime)
AND (t2.ServiceID <> 0 and c.ServiceID <> 0)
AND (t2.StartTime >= #StartTime AND t2.EndTime <= #EndTime)
AND (t2.ProvidedBy = c.ProvidedBy)
) oa)
SELECT c.ProvidedBy,
c.ServiceStart,
c.ServiceEnd,
MAX(ca1.ClaimIDs) ClaimIDs,
MAX(ca2.ClaimDetailIDs) ClaimDetailIDs,
MAX(ca3.ServiceIDs) ServiceIDs
FROM cte c
CROSS APPLY
(
SELECT STUFF(
(
SELECT ', ' + CAST(p.[ClaimID] AS VARCHAR(40))
FROM cte AS p
WHERE p.ServiceStart = c.ServiceStart AND (p.ProvidedBy = c.ProvidedBy)
AND p.ServiceID <> 0
ORDER BY p.ClaimID FOR XML PATH('')
), 1, 1, '')
) ca1(ClaimIDs)
CROSS APPLY
(
SELECT STUFF(
(
SELECT ', ' + CAST(p.[ClaimDetailID] AS VARCHAR(40))
FROM cte AS p
WHERE p.ServiceStart = c.ServiceStart AND (p.ProvidedBy = c.ProvidedBy)
AND p.ServiceID <> 0
ORDER BY p.ClaimDetailID FOR XML PATH('')
), 1, 1, '')
) ca2(ClaimDetailIDs)
CROSS APPLY
(
SELECT STUFF(
(
SELECT ', ' + CAST(p.[ServiceID] AS VARCHAR(40))
FROM cte AS p
WHERE p.ServiceStart = c.ServiceStart AND (p.ProvidedBy = c.ProvidedBy)
AND p.ServiceID <> 0
ORDER BY p.ClaimDetailID FOR XML PATH('')
), 1, 1, '')
) ca3(ServiceIDs)
GROUP BY c.ProvidedBy,
c.ServiceStart,
c.ServiceEnd
ORDER BY c.ProvidedBy,
c.ServiceStart,
c.ServiceEnd;
The problem is: I'm getting null rows back as shown
The desired result is: As shown here with complete code.

Return tag string based on two conditions

I have a table with a Tags column, which contains items like so: 'server, network, location1'...
I need to find all records where the item 'storage' is present
AND
where any of these locations are present ('location1', location2, location3')
AND
where no other items are present (i.e. only 'storage' and a location OR only 'storage').
I'm using a function to split the string into items, so a select statement for the code below using Tags for the entire string and Item for the items will help me a lot.
SELECT cardid, item, tags, count(1) as Total
, result =
case when lower(item) = 'storage' and
lower(item) in 'location1, location2, location3'
then 'yes'
else 'no'
end
FROM myTable
CROSS APPLY dbo.fn_SplitString(Tags, ',')
GROUP BY cardid, item, tags
order by item desc
Here is the function I use:
CREATE FUNCTION [dbo].[fn_SplitString]
(
#String VARCHAR(8000),
#Delimiter VARCHAR(255)
)
RETURNS
#Results TABLE
(
ID INT IDENTITY(1, 1),
Item VARCHAR(8000)
)
AS
BEGIN
INSERT INTO #Results (Item)
SELECT SUBSTRING(#String+#Delimiter, Number,
CHARINDEX(#Delimiter, #String+#Delimiter, Number) - Number)
FROM Numbers
WHERE Number <= LEN(REPLACE(#String,' ','|'))
AND SUBSTRING(#Delimiter + #String,
Number,
LEN(REPLACE(#delimiter,' ','|'))) = #Delimiter
ORDER BY Number RETURN
END
"to find all records where ..." your query should be changed to this:
SELECT MT.cardid, MT.tags, count(1) as Total
FROM myTable AS MT
CROSS APPLY dbo.fn_SplitString(Tags, ',') AS TagList
GROUP BY MT.cardid, MT.tags
HAVING COUNT( * ) =
COUNT( CASE WHEN TagList.Item IN( 'location1', 'location2', 'location3' ) THEN 1 ELSE NULL END )
+ COUNT( CASE WHEN TagList.Item = 'storage' THEN 1 ELSE NULL END )
AND COUNT( CASE WHEN TagList.Item = 'storage' THEN 1 ELSE NULL END ) >= 1
ORDER BY MT.cardid DESC
The above query is using "Conditional Aggregation". I have added HAVING clause where for each group COUNT of all tags is compared to the sum of "location" tags COUNT and "storage" tag COUNT. The next condition checks that the COUNT of "storage" is greater or equal to 1 (if you need only 1 storage element than change it to = 1)
Note: I made an assumption that your dbo.fn_SplitString function returns a table with a column called "Item".
Note 2: notice that I have given an alias ("AS TagList") to the function.
If you want the query to return all rows and simply indicate if a row matches your tag condition (like the example query you have given) then move all HAVING conditions to your CASE statement and remove HAVING
SELECT MT.cardid, MT.tags, COUNT(*) as Total
, result =
CASE
WHEN COUNT( * ) =
COUNT( CASE WHEN TagList.Value IN( 'location1', 'location2', 'location3' ) THEN 1 ELSE NULL END )
+ COUNT( CASE WHEN TagList.Value = 'storage' THEN 1 ELSE NULL END )
AND COUNT( CASE WHEN TagList.Item = 'storage' THEN 1 ELSE NULL END ) >= 1
THEN 'yes'
ELSE 'no'
END
FROM myTable AS MT
CROSS APPLY dbo.fn_SplitString(Tags, ',') AS TagList
GROUP BY MT.cardid, MT.tags
ORDER BY MT.cardid DESC
Below is a working example:
DECLARE #Sample TABLE( GroupID INT, Tag VARCHAR( 20 ))
INSERT INTO #Sample( GroupID, Tag )
VALUES
( 1, 'location1' ),( 1, 'location2' ), ( 1, 'storage' ),
( 2, 'location1' ),( 2, 'location2' ), ( 2, 'something else' ),
( 3, 'storage' )
SELECT GroupID
FROM #Sample AS TagList
GROUP BY GroupID
HAVING COUNT( * ) =
COUNT( CASE WHEN TagList.Tag IN( 'location1', 'location2', 'location3' ) THEN 1 ELSE NULL END )
+ COUNT( CASE WHEN TagList.Tag = 'storage' THEN 1 ELSE NULL END )
AND COUNT( CASE WHEN TagList.Tag = 'storage' THEN 1 ELSE NULL END ) >= 1
In the future please provide a sample table like the one above.
Update (to answer your comments):
Notice that the function below is a table valued function which returns a table with one column: #Results TABLE( Value VARCHAR( MAX )). The name of the column is "Value" (in your case it may be a different name). So in your query to access data returned by this function you specify the name of this column (in my case it is "Value").
CREATE FUNCTION dbo.fn_SplitString
(
#Source VARCHAR( MAX ),
#Delimiter VARCHAR( 100 )
)
RETURNS #Results TABLE( Value VARCHAR( MAX ))
AS
BEGIN
-- The main body of code has been excluded for brevity
END
Links to similar questions that contain explanations:
T-SQL SUM All with a Conditional COUNT
Create a view with totals from multiple columns
SQL Server - conditional aggregation with correlation

Common Table Expressions to retrieve path

I'm trying to use a recursive query to find a path through a table structured like this:
RelatedEntities
FromKey TINYINT
ToKey TINYINT
...more....
I thought I could do something like this:
DECLARE #startKey UNIQUEIDENTIFIER, #endKey UNIQUEIDENTIFIER;
SET #startKey = 0;
SET #endKey = 3;
;With findPath
AS
(
SELECT FromKey, ToKey
FROM RelatedEntities
WHERE FromKey = #startKey
UNION ALL
SELECT FromKey, ToKey
FROM RelatedEntities r
JOIN findPath b
ON r.FromKey = b.ToKey
AND r.FromKey NOT IN (SELECT FromKey FROM b)
)
SELECT * FROM findPath;
This code fails because I cannot use a subquery within a CTE. It also seems to be a rule that the recursive query can only contain one reference to the CTE. (true?) Maybe this is a job for a cursor, or procedural code, but I thought I would put it out here in case I'm missing a way to find a path through a table with a CTE?
The parameters are:
Start with a beginning and ending key
Base query uses the beginning key
Recursive query should stop when it contains the ending key, (have not been able to figure that one out) and should not repeat start keys.
A MAXRECURSION option could be used to stop after a certain number of iterations.
Thanks to all of you CTE gurus out there. Set me straight.
Changing this from UNIQUEIDENTIFIERS to TINYINT for readability. The SQL constructs are the same. Here's some code to test it.
CREATE TABLE RelatedEntities(FromKey TINYINT, ToKey TINYINT);
INSERT RelatedEntities(FromKey, ToKey)
VALUES
(1, 0),
(0, 1),
(1, 7),
(7, 1),
(3, 4),
(4, 3)
;With FindPath
AS
(
SELECT FromKey, ToKey, 0 AS recursionLevel
FROM RelatedEntities
WHERE FromKey = 1
UNION ALL
SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel + 1
FROM RelatedEntities r
INNER JOIN FindPath b ON r.FromKey = b.ToKey
WHERE b.ToKey <> 3 AND RecursionLevel < 10
)
SELECT * FROM FindPath
ORDER BY recursionLevel
Note that this returns from 1, to 0, then from 0, to 1, and repeats until I run out of recursion levels.
You need to modify your query like this:
DECLARE #startKey UNIQUEIDENTIFIER, #endKey UNIQUEIDENTIFIER;
DECLARE #maxRecursion INT = 100
SET #startKey = '00000000-0000-0000-0000-000000000000';
SET #endKey = 'F7801327-C037-AA93-67D1-B7892F6093A7';
;With FindPath
AS
(
SELECT FromKey, ToKey, 0 AS recursionLevel
FROM RelatedEntities
WHERE FromKey = #startKey
UNION ALL
SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel +1
FROM RelatedEntities r
INNER JOIN FindPath b ON r.FromKey = b.ToKey
WHERE b.ToKey <> #endKey AND recursionLevel < #maxRecursion
)
SELECT * FROM FindPath;
The anchor member of the above CTE:
SELECT FromKey, ToKey, 0 AS recursionLevel
FROM RelatedEntities
WHERE FromKey = #startKey
will select the starting record, T0, of the (From, To) chain of records.
The recursive member of the CTE:
SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel +1
FROM RelatedEntities r
INNER JOIN FindPath b ON r.FromKey = b.ToKey
WHERE b.ToKey <> #endKey AND recursionLevel < #maxRecursion
will be executed with T0, T1, ... as an input and T1, T2, ... respectively as an output.
This process will continue adding records to the final result set until an empty set is returned from the recursive member, i.e. until a record with ToKey=#endKey has been added to the result set, or #maxRecursion level has been reached.
EDIT:
You can use the following query in order the effectively handle any circular paths:
;With FindPath
AS
(
SELECT FromKey, ToKey,
0 AS recursionLevel,
CAST(FromKey AS VARCHAR(MAX)) AS FromKeys
FROM RelatedEntities
WHERE FromKey = 1
UNION ALL
SELECT r.FromKey, r.ToKey,
recursionLevel = recursionLevel + 1,
FromKeys = FromKeys + ',' + CAST(r.FromKey AS VARCHAR(MAX))
FROM RelatedEntities r
INNER JOIN FindPath b ON r.FromKey = b.ToKey
WHERE (b.ToKey <> 3)
AND (RecursionLevel < 10)
AND PATINDEX('%,' + CAST(r.ToKey AS VARCHAR(MAX)) + ',%', ',' + FromKeys + ',') = 0
)
SELECT * FROM FindPath
ORDER BY recursionLevel
Calculated field FromKeys is used to carry on FromKey on to the next recursion level. This way any keys from previous recursion levels are accumulated from level to level using string concatenation. PATINDEX is then used to check whether a circular path has been met.
SQL Fiddle Demo here

Remove second appearence of a substring from string in SQL Server

I need to remove the second appearance of a substring from the main string, IF both substrings are next to each other. e.g.:
Jhon\Jhon\Jane\Mary\Bob needs to end Jhon\Jane\Mary\Bob
but Mary\Jane\Mary\Bob has to remain unchanged.
Can anyone can come out with a performant way to do this?
'\' is the separator of different names, so it can be use as limit of the substring to replace.
EDIT: this is to be run on a SELECT statement, so it should be a one line solution, I can't use variables.
Also, if the names are repetaed anywhere else, I have to let them there. Only remove one occurrence if both the first and the second names are the same.
So here is one try, but as I said, I don't think you will get a fast solution in native T-SQL.
First, if you don't already have a numbers table, create one:
SET NOCOUNT ON;
DECLARE #UpperLimit int = 4000;
;WITH n AS
(
SELECT rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
)
SELECT [Number] = rn - 1
INTO dbo.Numbers FROM n
WHERE rn <= #UpperLimit + 1;
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers([Number]);
Then create two functions. One that splits strings apart into a table, and then another that re-joins the results of the first function but ignores any subsequent duplicates.
CREATE FUNCTION dbo.SplitStrings
(
#List nvarchar(4000),
#Delim char(1)
)
RETURNS TABLE
AS
RETURN ( SELECT
rn = ROW_NUMBER() OVER (ORDER BY CHARINDEX(#Delim, #List + #Delim)),
[Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim, #List + #Delim, [Number]) - [Number])))
FROM dbo.Numbers
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim + #List, [Number], 1) = #Delim
);
GO
Second function:
CREATE FUNCTION dbo.RebuildString
(
#List nvarchar(4000),
#Delim char(1)
)
RETURNS nvarchar(4000)
AS
BEGIN
RETURN ( SELECT newval = STUFF((
SELECT #Delim + x.[Value] FROM dbo.SplitStrings(#List, #Delim) AS x
LEFT OUTER JOIN dbo.SplitStrings(#List, #Delim) AS x2
ON x.rn = x2.rn + 1
WHERE (x2.rn IS NULL OR x.value <> x2.value)
ORDER BY x.rn
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
);
END
GO
Now you can try it against the two samples you gave in your question:
;WITH cte(colname) AS
(
SELECT 'Jhon\Jhon\Jane\Mary\Bob'
UNION ALL SELECT 'Mary\Jane\Mary\Bob'
)
SELECT dbo.RebuildString(colname, '\')
FROM cte;
Results:
Jhon\Jane\Mary\Bob
Mary\Jane\Mary\Bob
But I strongly, strongly, strongly recommend you thoroughly test this against your typical data size before deciding to use it.
I decided to go for string manipulation. I thought it'd take longer to execute the query, but testing it in... ejem... production environment... ejem... I found out that it did not (much to my surprise). It ain't pretty, I know, but it's easy to mantain...
Here is a simplified version of my final query:
SELECT SOQ.PracticeId,
CASE WHEN LEFT(SOQ.myString, SOQ.SlashPos) = SUBSTRING(SOQ.myString, SOQ.SlashPos + 1, LEN(LEFT(SOQ.myString, SOQ.SlashPos)))
THEN RIGHT(SOQ.myString, LEN(SOQ.myString) - SOQ.SlashPos)
ELSE SOQ.myString
END as myString
FROM (SELECT OQ.AllFields, OQ.myString, CHARINDEX('\', OQ.myString, 0) as SlashPos
FROM MyOriginalQuery OQ) SOQ

How to parse a string and create several columns from it?

I have a varchar(max) field containing Name Value pairs, in every line I have Name UnderScore Value.
I need to do a query against it so that it returns the Name, Value pairs in two columns (so by parsing the text, removing the underscore and the "new line" char.
So from this
select NameValue from Table
where I get this text:
Name1_Value1
Name2_Value2
Name3_Value3
I would like to have this output
Names Values
===== ======
Name1 Value1
Name2 Value2
Name3 Value3
SELECT substring(NameValue, 1, charindex('_', NameValue)-1) AS Names,
substring(NameValue, charindex('_', NameValue)+1, LEN(NameValue)) AS Values
FROM Table
EDIT:
Something like this put in a function or stored procedure combined with a temp table should work for more than one line, depending on the line delimiter you should also remove CHAR(13) before you start:
DECLARE #helper varchar(512)
DECLARE #current varchar(512)
SET #helper = NAMEVALUE
WHILE CHARINDEX(CHAR(10), #helper) > 0 BEGIN
SET #current = SUBSTRING(#helper, 1, CHARINDEX(CHAR(10), #helper)-1)
SELECT SUBSTRING(#current, 1, CHARINDEX('_', #current)-1) AS Names,
SUBSTRING(#current, CHARINDEX('_', #current)+1, LEN(#current)) AS Names
SET #helper = SUBSTRING(#helper, CHARINDEX(CHAR(10), #helper)+1, LEN(#helper))
END
SELECT SUBSTRING(#helper, 1, CHARINDEX('_', #helper)-1) AS Names,
SUBSTRING(#helper, CHARINDEX('_', #helper)+1, LEN(#helper)) AS Names
DECLARE #TExt NVARCHAR(MAX)= '***[ddd]***
dfdf
fdfdfdfdfdf
***[fff]***
4545445
45454
***[ahaASSDAD]***
DFDFDF
***[SOME TEXT]***
'
DECLARE #Delimiter VARCHAR(1000)= CHAR(13) + CHAR(10) ;
WITH numbers
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY o.object_id, o2.object_id ) Number
FROM sys.objects o
CROSS JOIN sys.objects o2
),
c AS ( SELECT Number CHARBegin ,
ROW_NUMBER() OVER ( ORDER BY number ) RN
FROM numbers
WHERE SUBSTRING(#text, Number, LEN(#Delimiter)) = #Delimiter
),
res
AS ( SELECT CHARBegin ,
CAST(LEFT(#text, charbegin) AS NVARCHAR(MAX)) Res ,
RN
FROM c
WHERE rn = 1
UNION ALL
SELECT c.CHARBegin ,
CAST(SUBSTRING(#text, res.CHARBegin,
c.CHARBegin - res.CHARBegin) AS NVARCHAR(MAX)) ,
c.RN
FROM c
JOIN res ON c.RN = res.RN + 1
)
SELECT *
FROM res
He is an example that you can use:
-- Creating table:
create table demo (dID int, dRec varchar(100));
-- Inserting records:
insert into demo (dID, dRec) values (1, 'BCQP1 Sam');
insert into demo (dID, dRec) values (2, 'BCQP2 LD');
-- Selecting fields to retrive records:
select * from demo;
Then I want to show in one single row both rows combined and display only the values from the left removing the name on the right side up to the space character.
/*
The STUFF() function puts a string in another string, from an initial position.
The LEFT() function returns the left part of a character string with the specified number of characters.
The CHARINDEX() string function returns the starting position of the specified expression in a character string.
*/
SELECT
DISTINCT
STUFF((SELECT ' ' + LEFT(dt1.dRec, charindex(' ', dt1.dRec) - 1)
FROM demo dt1
ORDER BY dRec
FOR XML PATH('')), 1, 1, '') [Convined values]
FROM demo dt2
--
GROUP BY dt2.dID, dt2.dRec
ORDER BY 1
As you can see here when you run the function the output will be:
BCQP1 BCQP2
On the top of the script I explained what each function is used for (STUFF(), LEFT(), CHARINDEX() functions) I also used DISTINCT in order to eliminate duplicate values.
NOTE: dt stands for "demo table", I used the same table and use two alias dt1 and dt2, and dRec stands for "demo Record"
If you want to learn more about STUFF() Function here is a link:
https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/
With a CTE you will have a problem with Recursion if more that 100 items
Msg 530, Level 16, State 1, Line 20 The statement terminated. The
maximum recursion 100 has been exhausted before statement completion.
DECLARE #TExt NVARCHAR(MAX)
SET #TExt = '100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203'
DECLARE #Delimiter VARCHAR(1000)= ',';
WITH numbers
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY o.object_id, o2.object_id ) Number
FROM sys.objects o
CROSS JOIN sys.objects o2
),
c AS ( SELECT Number CHARBegin ,
ROW_NUMBER() OVER ( ORDER BY number ) RN
FROM numbers
WHERE SUBSTRING(#text, Number, LEN(#Delimiter)) = #Delimiter
),
res
AS ( SELECT CHARBegin ,
CAST(LEFT(#text, charbegin) AS NVARCHAR(MAX)) Res ,
RN
FROM c
WHERE rn = 1
UNION ALL
SELECT c.CHARBegin ,
CAST(SUBSTRING(#text, res.CHARBegin,
c.CHARBegin - res.CHARBegin) AS NVARCHAR(MAX)) ,
c.RN
FROM c
JOIN res ON c.RN = res.RN + 1
)
SELECT *
FROM res

Resources