-- The simple fixed-length function, which will return incorrect results,
-- if the list length exceeds the highest number in Numbers.
CREATE FUNCTION fixstring_single(@str nvarchar(MAX), @itemlen tinyint)
RETURNS TABLE
RETURN(SELECT listpos = n.Number,
              str = rtrim(convert(nvarchar(4000),
                      substring(@str, @itemlen * (n.Number - 1) + 1, @itemlen)))
       FROM   Numbers n
       WHERE  n.Number <= len(@str) / @itemlen +
              CASE len(@str) % @itemlen WHEN 0 THEN 0 ELSE 1 END)
go

-- The same as fixstring_single, but as a multi-statement function. CREATE FUNCTION fixstring_mstmt(@str nvarchar(MAX), @itemlen tinyint) RETURNS @t TABLE (listpos int NOT NULL, str nvarchar(4000) NOT NULL) AS BEGIN INSERT @t (listpos, str) SELECT listpos = n.Number, str = rtrim( substring(@str, @itemlen * (n.Number - 1) + 1, @itemlen)) FROM Numbers n WHERE n.Number <= len(@str) / @itemlen + CASE len(@str) % @itemlen WHEN 0 THEN 0 ELSE 1 END RETURN END go
-- A special version for integers that works on a binary string. CREATE FUNCTION fixbinary_single(@str varbinary(MAX)) RETURNS TABLE AS RETURN(SELECT listpos = n.Number, n = convert(int, substring(@str, 4 * (n.Number - 1) + 1, 4)) FROM Numbers n WHERE n.Number <= datalength(@str) / 4 ) go
-- A more complex fixed-length function that self-join Numbers to increase -- the longest supported list length to the practical infinity. CREATE FUNCTION fixstring_multi(@str nvarchar(MAX), @itemlen tinyint) RETURNS TABLE AS RETURN(SELECT listpos = n1.Number + m.maxnum * (n2.Number - 1), str = rtrim(convert(nvarchar(4000), substring(@str, @itemlen * (n1.Number + m.maxnum * (n2.Number - 1) - 1) + 1, @itemlen))) FROM Numbers n1 CROSS JOIN (SELECT maxnum = MAX(Number) FROM Numbers) AS m JOIN Numbers n2 ON @itemlen * (n1.Number + m.maxnum * (n2.Number - 1) - 1) + 1 <= len(@str) WHERE n2.Number <= len(@str) / (m.maxnum * @itemlen) + 1 AND n1.Number <= CASE WHEN len(@str) / @itemlen <= m.maxnum THEN len(@str) / @itemlen + CASE len(@str) % @itemlen WHEN 0 THEN 0 ELSE 1 END ELSE m.maxnum END ) go
-- The same idea as in fixstring_multi, cross-join Numbers to get more -- numbers, but written somewhat differently. CREATE FUNCTION fixstring_multi2(@str nvarchar(MAX), @itemlen tinyint) RETURNS TABLE AS RETURN( SELECT listpos = (s.sliceno - 1) * (s.maxnum / @itemlen) + n.Number, str = rtrim(convert(nvarchar(4000), substring(s.slice, @itemlen * (n.Number - 1) + 1, @itemlen))) FROM (SELECT m.maxnum, sliceno = n.Number, slice = substring(@str, (m.maxnum - m.maxnum % @itemlen) * (n.Number - 1) + 1, m.maxnum - m.maxnum % @itemlen) FROM Numbers n CROSS JOIN (SELECT maxnum = MAX(Number) FROM Numbers) AS m WHERE n.Number <= len(@str) / (m.maxnum - m.maxnum % @itemlen) + CASE len(@str) % (m.maxnum - m.maxnum % @itemlen) WHEN 0 THEN 0 ELSE 1 END) AS s JOIN Numbers n ON n.Number <= len(s.slice) / @itemlen + CASE len(s.slice) % @itemlen WHEN 0 THEN 0 ELSE 1 END ) go
-- The iterative method, but with fixed strings. CREATE FUNCTION fixstring_iter(@str nvarchar(MAX), @itemlen tinyint) RETURNS @tbl TABLE (listpos int NOT NULL, Value nvarchar(4000) NOT NULL) AS BEGIN DECLARE @i int, @pos int, @strlen int SELECT @i = 1, @pos = 1, @strlen = len(@str) WHILE @pos <= @strlen BEGIN INSERT @tbl (listpos, Value) SELECT @i, rtrim(substring(@str, @pos, @itemlen)) SELECT @i = @i + 1, @pos = @pos + @itemlen END RETURN END