There are many answers already, most of them working for this tiny case, but all of them not unproblematic:
- The quirky update has side-effects one should really deeply know about
- All XML-approaches get in troubles with escaped characters like
& -> &
- All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode
Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:
DECLARE @Word1 NVARCHAR(100)='train';
DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML
DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));
--Tally (a set of running numbers)
--Best was an existing (physical) numbers table, but this shows the principles
WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
INSERT INTO @singleChars(WordId,Chr)
SELECT 1,SUBSTRING(@Word1,nmbr,1)
FROM tally;
--recursive
--This is a hidden RBAR approach (rather iterativ than recursive)
WITH cte AS
(
SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
UNION ALL
SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
FROM cte
WHERE cte.ChrPos+1<=LEN(@Word2)
)
INSERT INTO @singleChars(WordId,Chr)
SELECT 2,Chr
FROM cte
ORDER BY ChrPos;
--check the result
SELECT * FROM @singleChars;
--Now we re-concatenate the single characters.
--XML approach
SELECT (SELECT sc1.Chr
FROM @singleChars sc1
WHERE sc.WordId=sc1.WordId
ORDER BY ID --never forget the "ORDER BY"!
FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
FROM @singleChars sc
GROUP BY WordId;
--STRING_AGG(needs v2017+)
SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
FROM @singleChars sc
GROUP BY WordId