This is an approach without a loop...
Besides the fact, that loops are something to avoid, this makes the handling of a full table really easy.
First I create a mock-up table with three strings
DECLARE @tbl TABLE(ID INT IDENTITY,YourString VARCHAR(100));
INSERT INTO @tbl VALUES('ServiceProviderReferenceNumber'),('SomeOther'),('AndOneMore');
The query will first create a numbers tally table on the fly, then split the string in single chars, check for capitalized letters and add a blank.
Finally this is reconcatenated and trimmed
Attention If you might need more than 1000 characters, just add one more ,Numbers AS c
to the Tally
(providing 10.000 numbers then)
WITH Numbers AS
(SELECT Nr FROM(SELECT Nr FROM(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS x(Nr)) AS y)
,Tally AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr
FROM Numbers,Numbers AS a,Numbers AS b
)
,Splitted AS
(
SELECT ID
,CASE WHEN ASCII(Chars.OneChar) BETWEEN ASCII('A') AND ASCII('Z') THEN ' ' + Chars.OneChar ELSE Chars.OneChar END AS TheChar
FROM @tbl AS t
CROSS APPLY (SELECT TOP(LEN(t.YourString)) Tally.Nr FROM Tally) AS Nmbr
CROSS APPLY (SELECT SUBSTRING(t.YourString,Nmbr.Nr,1) AS OneChar) AS Chars
)
SELECT ID
,LTRIM((
SELECT s.TheChar AS [*]
FROM Splitted AS s
WHERE s.ID=Splitted.ID
FOR XML PATH('')
)) AS ReConcatenated
FROM Splitted
GROUP BY ID
The result
1 Service Provider Reference Number
2 Some Other
3 And One More
UPDATE Comparison of ad-hoc vs. loop
CREATE DATABASE testDB;
GO
USE testDB;
GO
CREATE TABLE tbl(ID INT IDENTITY,YourString VARCHAR(100));
GO
CREATE FUNCTION dbo.TestF(@String VARCHAR(4000))
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE @StringNew nvarchar(100) = '';
DECLARE @Char nvarchar(1);
DECLARE @len int = LEN(@String);
DECLARE @i int = 0;
WHILE @i <= @len
BEGIN
SET @i = @i+1;
SET @Char = substring(@String,@i,1);
IF (UNICODE(@Char) = UNICODE(UPPER(@Char)) AND @i > 1)
SET @StringNew = @StringNew + ' ' + @Char;
ELSE
SET @StringNew = @StringNew + @Char;
END;
RETURN @StringNew
END
GO
INSERT INTO tbl VALUES('ServiceProviderReferenceNumber'),('SomeOther'),('AndOneMore');
GO 100000
DECLARE @d DATETIME=GETDATE();
WITH Numbers AS
(SELECT Nr FROM(SELECT Nr FROM(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS x(Nr)) AS y)
,Tally AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr
FROM Numbers,Numbers AS a,Numbers AS b
)
,Splitted AS
(
SELECT ID
,CASE WHEN ASCII(Chars.OneChar) BETWEEN ASCII('A') AND ASCII('Z') THEN ' ' + Chars.OneChar ELSE Chars.OneChar END AS TheChar
FROM tbl AS t
CROSS APPLY (SELECT TOP(LEN(t.YourString)) Tally.Nr FROM Tally) AS Nmbr
CROSS APPLY (SELECT SUBSTRING(t.YourString,Nmbr.Nr,1) AS OneChar) AS Chars
)
SELECT ID
,LTRIM((
SELECT s.TheChar AS [*]
FROM Splitted AS s
WHERE s.ID=Splitted.ID
FOR XML PATH('')
)) AS ReConcatenated
FROM Splitted
GROUP BY ID;
SELECT CAST(GETDATE()-@d AS TIME);
GO
DECLARE @d DATETIME=GETDATE();
SELECT ID,dbo.TestF(tbl.YourString) AS ReConcatenated
FROM tbl
SELECT CAST(GETDATE()-@d AS TIME);
GO
USE master;
GO
DROP DATABASE testDB;
The result
**ad-hoc** 2.66 Seconds
**loop** 5.33
The result
**ad-hoc** 2.66 Seconds
**loop** 5.33
**while with `STUFF`** 1.71