21

I have searched everywhere and I cannot find this implementation anywhere.

Let's say I have the word: QWERTY

I want to obtain this table:

Q
W
E
R
T
Y

Or for QWERTY AnotherWord I want to obtain

Q
W
E
R
T
Y
[space character here]
A
n
o
t
h
e
r
W
o
r
d
Tony
  • 9,672
  • 3
  • 47
  • 75
Dragos Durlut
  • 8,018
  • 10
  • 47
  • 62
  • This is a duplicate of http://stackoverflow.com/questions/697519/split-function-equivalent-in-tsql – Petar Ivanov Dec 15 '11 at 09:34
  • 2
    Sorry, the google and stackoverflow search didn't return any userful results. On the other side, I see that the answer given here is better and faster and it better fits my need of a solution. Also, I think it is more straight forward. – Dragos Durlut Dec 15 '11 at 09:59
  • 6
    @PetarIvanov This is not a duplicate. The question you linked is about splitting values on a *delimiter*. There is no delimiter in use here. – Jonathan Wilson Mar 11 '19 at 19:28

7 Answers7

40

Do it like this:

select substring(a.b, v.number+1, 1) 
from (select 'QWERTY AnotherWord' b) a
join master..spt_values v on v.number < len(a.b)
where v.type = 'P'
aF.
  • 64,980
  • 43
  • 135
  • 198
t-clausen.dk
  • 43,517
  • 12
  • 59
  • 92
  • hi @t-clausen.dk, the solution is not working in azure sql database. Do you have any other options? – Magendran V Nov 27 '17 at 14:16
  • 1
    @MagendranV it's because azure doesn't have spt_values, but its only purpose here is to act as a list of numbers up to 2047; use a row generator instead, like BabiBN's answer (safer than relying on undocumented spt_values) – Caius Jard Apr 27 '20 at 19:40
  • combining the answer from https://stackoverflow.com/questions/21425546/ you can use following query as option in Azure SQL database select substring(a.b, v.number+1, 1) from (select 'QWERTY AnotherWord' b) a join ( SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n number FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n), (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n) ) v on v.number < len(a.b) ORDER BY v.number – Sam Jul 31 '21 at 21:25
9
Declare @word nvarchar(max)
Select @word = 'Hello This is the test';

with cte (Number)as 
(Select 1
union all 
select Number +1 From cte  where number <len(@word)
)
select * from Cte Cross apply (Select SUBSTRING(@word,number,1 ) ) as J(Letter)
BabiBN
  • 91
  • 1
  • 2
  • 1
    Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others. – Peter Jun 28 '17 at 08:50
  • Good solution, using cross apply and a cte. – Matthias Jan 19 '18 at 12:21
4

Here you have it:

create table #words (
  character varchar(1)
)

declare @test varchar(10)
select @test = 'QWERTY'

declare @count int, @total int
select @total = len(@test), @count = 0

while @count <= @total
begin
  insert into #words select substring(@test, @count, 1)
  select @count = @count + 1
end

select * from #words

drop table #words
aF.
  • 64,980
  • 43
  • 135
  • 198
3

Here is a table-valued function (derived from aF's temp table implementation). It differs slightly from aF's implementation in that it starts with @count=1; this excludes an extraneous leading space.

CREATE FUNCTION [dbo].[Chars] (@string VARCHAR(max)) 
RETURNS @chars TABLE (character CHAR) 
AS 
  BEGIN 
      DECLARE @count INT, 
              @total INT 

      SELECT @total = Len(@string), 
             @count = 1 

      WHILE @count <= @total 
        BEGIN 
            INSERT INTO @chars 
            SELECT Substring(@string, @count, 1) 

            SELECT @count = @count + 1 
        END 

      RETURN 
  END 

Usage:

SELECT * FROM dbo.chars('QWERTY AnotherWord')
Jonathan Wilson
  • 4,138
  • 1
  • 24
  • 36
1

Please, PLEASE avoid referencing systems tables, specifically system tables in system databases. In fact, the selected answer above probably won't compile in a Visual Studio 2013 Database Project

Table variables are fine, but recursion with a CTE is the answer:

DECLARE @str VARCHAR(max)
SET @str = 'QWERTY AnotherWord'
WITH Split(stpos,endpos)
AS(
SELECT 1 AS stpos, 2 AS endpos
UNION ALL
SELECT endpos, endpos+1
FROM Split
WHERE endpos <= LEN(@str)
)
SELECT 
    'character' = SUBSTRING(@str,stpos,COALESCE(NULLIF(endpos,0),LEN(@str)+1)-stpos)
   ,'charindex' = stpos
FROM Split

That said, the use for the code above is to get a table full of letters representing different permissions for a user. That is not the way to do this. Make a table with an ID, a permission code and a description then make a linking table between the users table and the new permissions table. this gives you the same abilities and doesn't make you solve dumb problems like this.

1

I wanted to contribute my own solution to this problem.

Convert into table valued function as desired (and handle nulls however you wish)

DECLARE @str nvarchar(100) = 'QWERTY AnotherWord'
DECLARE @len int = LEN(@str)-1;

--create a string of len(@str)-1 commas
--because STRING_SPLIT will return n rows for n-1 commas
--split string to return a table of len(@str) rows
--provide an index column named [index]
WITH [rows] AS (
    SELECT 
        ROW_NUMBER() OVER (ORDER BY [value]) [index] 
    FROM STRING_SPLIT(REPLICATE(',', @len), ',')
), 
--for each row, take the index number
--and extract the character from that index
[split] AS (
    SELECT 
        [index],
        SUBSTRING(@str,[index],1) [char]
    FROM [rows]
)
--maintain the same order
--and return just the extracted characters
SELECT 
    --[index],
    [char]
FROM [split]
ORDER BY [index] ASC

output:

char
----
Q
W
E
R
T
Y
 
A
n
o
t
h
e
r
W
o
r
d
0

I like the use of REPLICATE() and substring in the answer by @drrollergator. I find value in the answer below, in accounting for:

  • The truncation to 8000 characters mentioned by Microsoft learn/docs. Explicitly casting to a larger datatype will avoid this.
  • the unordered ROW_NUMBER as mentioned in [https://stackoverflow.com/questions/44105691/row-number-without-order-by].

Sample SQL:

DECLARE @str NVARCHAR(MAX) = N'QWERTY AnotherWord'
SELECT
ss.[value]
FROM
(   SELECT TOP(LEN(@str))
        SUBSTRING(@str,n.[i],1) [value]
        ,n.[i]
    FROM ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT '.')) [i] FROM STRING_SPLIT(REPLICATE(CAST('.' AS VARCHAR(MAX)),LEN(@str) - 1),'.') ) n([i])
    /* [A.] Generate numbers equal to character count in @expression */
    ORDER BY n.[i]  
    /* [B.] Return 1-Char-Substring for each number/position */
) ss
James Risner
  • 5,451
  • 11
  • 25
  • 47
Sean
  • 91
  • 4