Вы можете использовать рекурсивный подход, например так:
A таблица-макет для имитации ориентированного на множество сценария
declare @tbl TABLE(ID INT IDENTITY, SomeComment VARCHAR(100),SomeString varchar(200));
INSERT INTO @tbl VALUES('3 Terms','Dear #NAMEOFGUEST# , we glad to see you #SOMEHOTEL# tomorrow. And even #AThirdOne# is here.')
,('1 Term','Dear #NAMEOFGUEST# , we glad to see you soon.')
,('No Term','Dear Guest, nice to see you.')
,('invalid 1','Dear Guest, nice to #see you.')
,('invalid ?','Dear #Guest, nice# to see you.');
declare @scanChar char(1)='#';
- запрос
WITH recCTE AS
(
SELECT t.ID
,t.SomeComment
,t.SomeString AS TextToWork
,1 AS TermIndex
,D.*
FROM @tbl t
OUTER APPLY(SELECT CHARINDEX(@scanChar,t.SomeString)) A(StartingAt)
OUTER APPLY(SELECT CHARINDEX(@scanChar,t.SomeString,A.StartingAt+1)) B(EndingAt)
OUTER APPLY(SELECT CASE WHEN A.StartingAt>0 AND B.EndingAt >0 THEN SUBSTRING(t.SomeString,A.StartingAt+1,B.EndingAt- A.StartingAt-1) END) C(TermCandidate)
OUTER APPLY(SELECT A.StartingAt,B.EndingAt,C.TermCandidate,SUBSTRING(t.SomeString,B.EndingAt+1,1000) AS RestString) D
UNION ALL
SELECT t.ID
,t.SomeComment
,t.RestString
,t.TermIndex+1
,D.*
FROM recCTE t
OUTER APPLY(SELECT CHARINDEX(@scanChar,t.RestString)) A(StartingAt)
OUTER APPLY(SELECT CHARINDEX(@scanChar,t.RestString,A.StartingAt+1)) B(EndingAt)
OUTER APPLY(SELECT CASE WHEN A.StartingAt>0 AND B.EndingAt >0 THEN SUBSTRING(t.RestString,A.StartingAt+1,B.EndingAt- A.StartingAt-1) END) C(TermCandidate)
OUTER APPLY(SELECT A.StartingAt,B.EndingAt,C.TermCandidate,SUBSTRING(t.RestString,B.EndingAt+1,1000) AS RestString) D
WHERE (LEN(t.RestString) - LEN(REPLACE(t.RestString,@scanChar,'')))%2=0 AND CHARINDEX(@scanChar,t.RestString)>0
)
SELECT ID
,SomeComment
,TermIndex
--this will exclude "Guest, nice" due to the blank
,CASE WHEN CHARINDEX(' ',TermCandidate)>0 THEN NULL ELSE TermCandidate END AS Term
FROM recCTE
ORDER BY ID,TermIndex;
Результат
+----+-------------+-----------+-------------+
| ID | SomeComment | TermIndex | Term |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 1 | NAMEOFGUEST |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 2 | SOMEHOTEL |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 3 | AThirdOne |
+----+-------------+-----------+-------------+
| 2 | 1 Term | 1 | NAMEOFGUEST |
+----+-------------+-----------+-------------+
| 3 | No Term | 1 | NULL |
+----+-------------+-----------+-------------+
| 4 | invalid 1 | 1 | NULL |
+----+-------------+-----------+-------------+
| 5 | invalid ? | 1 | NULL |
+----+-------------+-----------+-------------+