Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

Using SQL Server 2005, how do I split a string so I can access item x?

For example, take the string "Hello John Smith". How can I split the string by a space and access the item at index 1 which should return "John"?

share|improve this question
2  
See stackoverflow.com/questions/314824/… as well – Jarrod Dixon Mar 8 '10 at 19:44

15 Answers

up vote 36 down vote accepted

You may find the solution in SQL User Defined Function to Parse a Delimited String helpful (from The Code Project).

U can use this simple logic -

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%',@products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products, 0, PATINDEX('%|%',@products))
        SELECT @individual

        SET @products = SUBSTRING(@products, LEN(@individual + '|') + 1, LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END
share|improve this answer
why SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText))) and not SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)? – Beth Sep 29 '10 at 15:13
2  
@GateKiller This solution does not support Unicode & it uses hard coded numeric(18,3) which doesn't make it a viable "reusable" function. – Filip De Vos Mar 18 '11 at 13:55

I don't believe SQL Server has a built-in split function, so other than a UDF, the only other answer I know is to hijack the PARSENAME function:

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 

PARSENAME takes a string and splits it on the period character. It takes a number as it's second argument, and that number specifies which segment of the string to return (working from back to front).

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

Obvious problem is when the string already contains a period. I still think using a UDF is the best way...any other suggestions?

share|improve this answer
7  
this is pretty clever – Saul Dolgin Jun 26 '09 at 18:32
41  
Thanks Saul...I should point out that this solution is really a bad solution for real development. PARSENAME only expects four parts, so using a string with more than four parts causes it to return NULL. The UDF solutions are obviously better. – Nathan Bedford Jul 1 '09 at 15:54
9  
This is a great hack, and also makes me weep that something like this is necessary for something so friggin simple in real languages. – Factor Mystic Jul 12 '10 at 14:09
3  
In my case, this is absolutely helpful! My data will never contain period. Thanks! – dpp Aug 2 '11 at 6:00
9  
To make the indexes work in the "right" way, that is, starting at 1, i've hijacked your hijack with REVERSE: REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) -- Returns Hello – NothingsImpossible May 14 '12 at 13:57
show 6 more comments

First, create a function (using CTE, common table expression does away with the need for a temp table)

 create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

Then, use it as any table (or modify it to fit within your existing stored proc) like this.

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

Update

Previous version would fail for input string longer than 4000 chars. This version takes care of the limitation:

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS Item
from tokens
);

GO

Usage remains the same.

share|improve this answer
+1 This is very nice – Adrian Carneiro Sep 19 '12 at 17:41
+1 Very elegant – guillegr123 Sep 26 '12 at 22:02
4  
It's elegant but only works for 100 elements because of the limit of recursion depth. – Pking Nov 7 '12 at 15:31
@Pking, no, the default is 100 (to prevent infinite loop). Use MAXRECURSION hint to define number of recursion levels (0 to 32767, 0 is "no limit" - may crush server). BTW, much better answer than PARSENAME, because it's universal :-). +1 – Michał Powaga Mar 14 at 14:45
Adding maxrecursion to this solution keep in mind this question and its answers How to setup the maxrecursion option for a CTE inside a Table-Valued-Function. – Michał Powaga Mar 15 at 9:03

Here is a UDF which will do it. It will return a table of the delimited values, haven't tried all scenarios on it but your example works fine.


CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
@myString varchar(500),
@deliminator varchar(10)
)
RETURNS
@ReturnTable TABLE
(
-- Add the column definitions for the TABLE variable here
[id] [int] IDENTITY(1,1) NOT NULL,
[part] [varchar](50) NULL
)
AS
BEGIN
Declare @iSpaces int
Declare @part varchar(50)

--initialize spaces
Select @iSpaces = charindex(@deliminator,@myString,0)
While @iSpaces > 0

Begin
Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

Insert Into @ReturnTable(part)
Select @part

Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))


Select @iSpaces = charindex(@deliminator,@myString,0)
end

If len(@myString) > 0
Insert Into @ReturnTable
Select @myString

RETURN
END
GO

You would call it like this:


Select * From SplitString('Hello John Smith',' ')

Edit: Updated solution to handle delimters with a len>1 as in :


select * From SplitString('Hello**John**Smith','**')
share|improve this answer
Didn't work for select * from dbo.ethos_SplitString_fn('guy,wicks,was here',',') id part ----------- -------------------------------------------------- 1 guy 2 wick – Guy Oct 20 '08 at 15:25
1  
watch out with len() as it'll not return correct number if its argument has trailing spaces., e.g. len(' - ') = 2. – Rory Oct 17 '09 at 16:30
Doesn't work on: select * from dbo.SplitString('foo,foo test,,,,foo',',') – cbp Apr 14 '10 at 5:14
1  
Fix for cbp.. Select @myString = substring(@mystring,@iSpaces + len(@deliminator),len(@myString) - charindex(@deliminator,@myString,0)) – Alxwest May 21 '12 at 10:12

No code, but read the definitive article on this. All solutions in other answers are flavours of the ones listed in this article: Arrays and Lists in SQL Server 2005 and Beyond

Personally, I've used a Numbers table solution most often because it suits what I have to do...

share|improve this answer

You can leverage a Number table to do the string parsing lightning fast:

    create function [dbo].[ufn_ParseArray]
    (	@Input		nvarchar(4000), 
    	@Delimiter	char(1) = ',',
    	@BaseIdent	int
    )
returns table as
return  
    (	select	row_number() over (order by n desc) + (@BaseIdent - 1) [i],
    			substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
    	from	dbo.Number
    	where	n <= convert(int, len(@Input)) and
    			substring(@Delimiter + @Input, n, 1) = @Delimiter
    )

Usage:

declare @Array varchar(500)
set @Array = 'Hello John smith'

select  s 
from    dbo.ufn_ParseArray(@Array, ' ', 0)
where   i = 1
share|improve this answer
The best solution IMO, the others have some kind of limitation.. this is fast and can parse long strings with many elements. – Pking Dec 6 '12 at 13:01

In my opinion you guys are making it way too complicated. Just create a CLR UDF and be done with it.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions {
  [SqlFunction]
  public static SqlString SearchString(string Search) {
    List<string> SearchWords = new List<string>();
    foreach (string s in Search.Split(new char[] { ' ' })) {
      if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
        SearchWords.Add(s);
      }
    }

    return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
  }
};
share|improve this answer
4  
I guess this is too much complicated, because I need to have Visual Studio, then enable CLR on the server, then create and compile the project, and finally add the assemblies to the database, in order to use it. But still is an interesting answer. – guillegr123 Sep 27 '12 at 13:55

Here I post a simple way of solution

CREATE FUNCTION [dbo].[split](
          @delimited NVARCHAR(MAX),
          @delimiter NVARCHAR(100)
        ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
        AS
        BEGIN
          DECLARE @xml XML
          SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'

          INSERT INTO @t(val)
          SELECT  r.value('.','varchar(MAX)') as item
          FROM  @xml.nodes('/t') as records(r)
          RETURN
        END


Execute the function like this

  select * from dbo.split('Hello John Smith',' ')
share|improve this answer
I liked this solution. Expanded it to return a scalar value based on the specified column within the results. – Alan Feb 22 at 22:21

Try this:

CREATE function [SplitWordList]
(
@list varchar(8000)
)
returns @t table
(
Word varchar(50) not null,
Position int identity(1,1) not null
)
as begin
declare
@pos int,
@lpos int,
@item varchar(100),
@ignore varchar(100),
@dl int,
@a1 int,
@a2 int,
@z1 int,
@z2 int,
@n1 int,
@n2 int,
@c varchar(1),
@a smallint
select
@a1 = ascii('a'),
@a2 = ascii('A'),
@z1 = ascii('z'),
@z2 = ascii('Z'),
@n1 = ascii('0'),
@n2 = ascii('9')
set @ignore = '''"'
set @pos = 1
set @dl = datalength(@list)
set @lpos = 1
set @item = ''
while (@pos <= @dl) begin
set @c = substring(@list, @pos, 1)
if (@ignore not like '%' + @c + '%') begin
set @a = ascii(@c)
if ((@a >= @a1) and (@a <= @z1))
or ((@a >= @a2) and (@a <= @z2))
or ((@a >= @n1) and (@a <= @n2))
begin
set @item = @item + @c
end else if (@item > '') begin
insert into @t values (@item)
set @item = ''
end
end
set @pos = @pos + 1
end
if (@item > '') begin
insert into @t values (@item)
end
return
end

Test it like this:

select * from SplitWordList('Hello John Smith')
share|improve this answer
I've gone through it & it is perfectly like what I want! even I can also customize it for ignoring special characters that I choose! – Vikas Sep 15 '10 at 6:57

Option with recursive CTE

CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(1000), @Delimiter nvarchar(1))
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH cte AS
 (
  SELECT SUBSTRING(@List, 0, CHARINDEX (@Delimiter, @List)) AS val ,
         CAST(STUFF(@List + @Delimiter, 1 , CHARINDEX (@Delimiter, @List), '') AS nvarchar(1000)) AS stval
  UNION ALL
  SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
         CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(1000))
  FROM cte
  WHERE stval != ''
  )
  SELECT REPLACE(val, ' ', '') AS val
  FROM cte

Demo on SQLFiddle

share|improve this answer

I know it's an old Question, but i think some one can benefit from my solution.

select 
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,1
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
    ,LEN(column_name))
from table_name

SQL FIDDLE

Advantages:

  • It separates all the 3 sub-strings deliminator by ' '.
  • One must not use while loop, as it decreases the performance.
  • No need to Pivot as all the resultant sub-string will be displayed in one Row

Limitations:

  • One must know the total no. of spaces (sub-string).

Note: the solution can give sub-string up to to N.

To overcame the limitation we can use the following ref.

But again the above solution can't be use in a table (Actaully i wasn't able to use it).

Again i hope this solution can help some-one.

Update: In case of Records > 50000 it is not advisable to use LOOPS as it will degrade the Performance

share|improve this answer

I was looking for the solution on net and the below works for me. Ref.

And you call the function like this : SELECT * FROM dbo.split('ram shyam hari gopal',' ')


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))       
RETURNS @temptable TABLE (items VARCHAR(8000))       
AS       
BEGIN       
DECLARE @idx INT       
DECLARE @slice VARCHAR(8000)        
SELECT @idx = 1       
IF len(@String)<1 OR @String IS NULL  RETURN       
while @idx!= 0       
BEGIN       
SET @idx = charindex(@Delimiter,@String)       
IF @idx!=0       
SET @slice = LEFT(@String,@idx - 1)       
ELSE       
SET @slice = @String       
IF(len(@slice)>0)  
INSERT INTO @temptable(Items) VALUES(@slice)       
SET @String = RIGHT(@String,len(@String) - @idx)       
IF len(@String) = 0 break       
END   
RETURN       
END
share|improve this answer

Well, mine isn't all that simpler, but here is the code I use to split a comma-delimited input variable into individual values, and put it into a table variable. I'm sure you could modify this slightly to split based on a space and then to do a basic SELECT query against that table variable to get your results.

-- Create temporary table to parse the list of accounting cycles.
DECLARE @tblAccountingCycles table
(
    AccountingCycle varchar(10)
)

DECLARE @vchAccountingCycle varchar(10)
DECLARE @intPosition int

SET @vchAccountingCycleIDs = LTRIM(RTRIM(@vchAccountingCycleIDs)) + ','
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)

IF REPLACE(@vchAccountingCycleIDs, ',', '') <> ''
BEGIN
    WHILE @intPosition > 0
    BEGIN
        SET @vchAccountingCycle = LTRIM(RTRIM(LEFT(@vchAccountingCycleIDs, @intPosition - 1)))
        IF @vchAccountingCycle <> ''
        BEGIN
            INSERT INTO @tblAccountingCycles (AccountingCycle) VALUES (@vchAccountingCycle)
        END
        SET @vchAccountingCycleIDs = RIGHT(@vchAccountingCycleIDs, LEN(@vchAccountingCycleIDs) - @intPosition)
        SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
    END
END

The concept is pretty much the same. One other alternative is to leverage the .NET compatibility within SQL Server 2005 itself. You can essentially write yourself a simple method in .NET that would split the string and then expose that as a stored procedure/function.

share|improve this answer
an example of doing it in .NET (CLR Procedures/functions) can be found here cstruter.com/blog/260 – cstruter Jan 28 '11 at 5:47

what about using string and 'values()' statement?

    declare @str varchar(max)
    set @str = 'Hello John Smith'

    declare @separator varchar(max)
    set @separator = ' '

    declare @Splited table(id int identity(1,1), item varchar(max))

    set @str = REPLACE(@str,@separator,'''),(''')
    set @str = 'select * from (values('''+@str+''')) as V(A)' 

    insert into @Splited
    exec(@str)

    select * from @Splited

resultset achieved..

    id  item
    1   Hello
    2   John
    3   Smith
share|improve this answer

Here's my solution that may help someone. Modification of Jonesinator's answer above.

If I have a string of delimited INT values and want a table of INTs returned (Which I can then join on). e.g. '1,20,3,343,44,6,8765'

Create a UDF:

IF OBJECT_ID(N'dbo.ufn_GetIntTableFromDelimitedList', N'TF') IS NOT NULL
    DROP FUNCTION dbo.[ufn_GetIntTableFromDelimitedList];
GO

CREATE FUNCTION dbo.[ufn_GetIntTableFromDelimitedList](@String NVARCHAR(MAX),                 @Delimiter CHAR(1))

RETURNS @table TABLE 
(
    Value INT NOT NULL
)
AS 
BEGIN
DECLARE @Pattern NVARCHAR(3)
SET @Pattern = '%' + @Delimiter + '%'
DECLARE @Value NVARCHAR(MAX)

WHILE LEN(@String) > 0
    BEGIN
        IF PATINDEX(@Pattern, @String) > 0
        BEGIN
            SET @Value = SUBSTRING(@String, 0, PATINDEX(@Pattern, @String))
            INSERT INTO @table (Value) VALUES (@Value)

            SET @String = SUBSTRING(@String, LEN(@Value + @Delimiter) + 1, LEN(@String))
        END
        ELSE
        BEGIN
            -- Just the one value.
            INSERT INTO @table (Value) VALUES (@String)
            RETURN
        END
    END

RETURN
END
GO

Then get the table results:

SELECT * FROM dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',')

1
20
3
343
44
6
8765

And in a join statement:

SELECT [ID], [FirstName]
FROM [User] u
JOIN dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',') t ON u.[ID] = t.[Value]

1    Elvis
20   Karen
3    David
343  Simon
44   Raj
6    Mike
8765 Richard

If you want to return a list of NVARCHARs instead of INTs then just change the table definition:

RETURNS @table TABLE 
(
    Value NVARCHAR(MAX) NOT NULL
)
share|improve this answer

protected by Will Aug 21 '10 at 16:38

This question is protected to prevent "thanks!", "me too!", or spam answers by new users. To answer it, you must have earned at least 10 reputation on this site.

Not the answer you're looking for? Browse other questions tagged or ask your own question.