STRING_SPLIT String Function

The `STRING_SPLIT` string function is a table-valued function that splits a string into rows of substrings, based on a specified separator character.

Compatibility level 130

`STRING_SPLIT` requires the compatibility level to be at least 130. When the level is less than 130, the Database Engine is unable to find the `STRING_SPLIT` function.

SELECT compatibility_level
FROM sys.databases WHERE name = 'SQLServerHelper';
| compatibility_level |
|---------------------|
|                 110 |
SELECT * FROM STRING_SPLIT('A,B,C', ',');
Query 1 ERROR at Line 22: : Msg: 208, Line 1, State: 1, Level: 16
Invalid object name 'STRING_SPLIT'.

Syntax

STRING_SPLIT ( string , separator [ , enable_ordinal ] )

Arguments

string

An expression of any character type (for example, nvarchar, varchar, nchar, or char).

separator

A single character expression of any character type (for example, nvarchar(1), varchar(1), nchar(1), or char(1)) that is used as separator for concatenated substrings.

enable_ordinal

An int or bit expression that serves as a flag to enable or disable the ordinal output column. A value of 1 enables the ordinal column. If enable_ordinal is omitted, NULL, or has a value of 0, the ordinal column is disabled.

Note: This option is only available to Azure SQL Database, Azure SQL Managed Instance, Azure Synapse Analytics (serverless SQL pool only), and SQL Server 2022 (16.x) and later versions

Return Types

If the ordinal output column isn't enabled, `STRING_SPLIT` returns a single-column table whose rows are the substrings. The name of the column is value. It returns nvarchar if any of the input arguments are either nvarchar or nchar. Otherwise, it returns varchar. The length of the return type is the same as the length of the string argument.

If the `enable_ordinal` argument is passed a value of 1, a second column named ordinal is returned that consists of the 1-based index values of each substring's position in the input string. The return type is bigint.

Sample Uses of the STRING_SPLIT String Function

Here are sample uses of the `STRING_SPLIT` string function

Usage #1 : Transpose a Table

WITH NBAChampions AS (
    SELECT *
    FROM (VALUES ('Boston Celtics', '2008,2024'),
                 ('Cleveland Cavaliers', '2016'),
                 ('Dallas Mavericks', '2011'),
                 ('Denver Nuggets', '2023'),
                 ('Detroit Pistons', '2004'),
                 ('Golden State Warriors', '2015,2017,2018,2022'),
                 ('Los Angeles Lakers', '2000,2001,2002,2009,2010,2020'),
                 ('Miami Heat', '2006,2012,2013'),
                 ('Milwaukee Bucks', '2021'),
                 ('San Antonio Spurs', '2003,2005,2007,2014'),
                 ('Toronto Raptors', '2019'))
    AS [NBATeams] ( [TeamName], [YearsWon])
)
SELECT [value] AS [Year], [TeamName]
FROM NBAChampions CROSS APPLY STRING_SPLIT([YearsWon], ',')
ORDER BY [value];
| Year  | TeamName          	|
|-------|-----------------------|
| 2000  | Los Angeles Lakers	|
| 2001  | Los Angeles Lakers	|
| 2002  | Los Angeles Lakers	|
| 2003  | San Antonio Spurs 	|
| 2004  | Detroit Pistons   	|
| 2005  | San Antonio Spurs 	|
| 2006  | Miami Heat        	|
| 2007  | San Antonio Spurs 	|
| 2008  | Boston Celtics    	|
| 2009  | Los Angeles Lakers	|
| 2010  | Los Angeles Lakers	|
| 2011  | Dallas Mavericks  	|
| 2012  | Miami Heat        	|
| 2013  | Miami Heat        	|
| 2014  | San Antonio Spurs 	|
| 2015  | Golden State Warriors |
| 2016  | Cleveland Cavaliers   |
| 2017  | Golden State Warriors |
| 2018  | Golden State Warriors |
| 2019  | Toronto Raptors   	|
| 2020  | Los Angeles Lakers	|
| 2021  | Milwaukee Bucks   	|
| 2022  | Golden State Warriors |
| 2023  | Denver Nuggets    	|
| 2024  | Boston Celtics    	|

Usage #2 : Sort a Comma-Delimited List of IPv4 Addresses

DECLARE @Input VARCHAR(1000)
DECLARE @Output VARCHAR(1000)

SET @Input = '10.0.0.1,192.120.40.243,207.46.199.60,255.255.0.0,255.255.255.255,64.233.188.15,98.123.251.21';

SELECT TOP 100 @Output = ISNULL(@Output + ',' + value, value)
FROM STRING_SPLIT(@Input, ',')
ORDER BY CAST(PARSENAME([value], 4) AS INT),
         CAST(PARSENAME([value], 3) AS INT),
         CAST(PARSENAME([value], 2) AS INT),
         CAST(PARSENAME([value], 1) AS INT);

SELECT @Output AS [Output]
| Output                                                                                    	|
|-----------------------------------------------------------------------------------------------|
| 10.0.0.1,64.233.188.15,98.123.251.21,192.120.40.243,207.46.199.60,255.255.0.0,255.255.255.255 |

Take note of the `TOP 100` clause in the query. Theoretically, that clause is not needed to generate the desired output but somehow it seems that there is bug with the `STRING_SPLIT` string function when the `value` column is assigned to a variable (in this case, the `@Output` variable) and the query includes an `ORDER BY` clause wherein the expression used is not just the `value` column by itself but an expression that uses operators.

Using the same query above but not include the `TOP 100` clause:

DECLARE @Input VARCHAR(1000)
DECLARE @Output VARCHAR(1000)

SET @Input = '10.0.0.1,192.120.40.243,207.46.199.60,255.255.0.0,255.255.255.255,64.233.188.15,98.123.251.21';

SELECT @Output = ISNULL(@Output + ',' + value, value)
FROM STRING_SPLIT(@Input, ',')
ORDER BY CAST(PARSENAME([value], 4) AS INT),
         CAST(PARSENAME([value], 3) AS INT),
         CAST(PARSENAME([value], 2) AS INT),
         CAST(PARSENAME([value], 1) AS INT);

SELECT @Output AS [Output];

The output of the query is as follows:

| Output          |
|-----------------|
| 255.255.255.255 |

Usage #3 : Sort a Comma-Delimited String

DECLARE @Input VARCHAR(1000)
DECLARE @Output VARCHAR(1000)

SET @Input = 'one,two,three,four,five,six,seven,eight,nine,ten'

SELECT @Output = ISNULL(@Output + ',' + value, value)
FROM STRING_SPLIT(@Input, ',')
ORDER BY value;

SELECT @Output AS [Output];
| Output                                           |
|--------------------------------------------------|
| eight,five,four,nine,one,seven,six,ten,three,two |

Usage #4 : Create a Integer Table

WITH Digits AS (
	SELECT CAST(value AS TINYINT) AS value
	FROM STRING_SPLIT('0,1,2,3,4,5,6,7,8,9', ',')
)
SELECT (tens.value * 10) + ones.value AS Number
FROM Digits tens CROSS JOIN Digits ones
ORDER BY Number;
| Number |
|--------|
| 0      |
| 1      |
| 2      |
| 3      |
| 4      |
| 5      |
| 6      |
| ...    |
| 95     |
| 96     |
| 97     |
| 98     |
| 99     |

Usage #5 : Parse a Query String

DECLARE @QueryString   VARCHAR(100)
SET @QueryString = 'street=1600+Pennsylvania+Avenue+NW&city=Washington&state=DC&zip=20500'
SELECT LEFT(value, CHARINDEX('=', value) - 1) AS [Key],
       RIGHT(value, CHARINDEX('=', REVERSE(value)) - 1) AS [Value]
FROM STRING_SPLIT(@QueryString, '&');
| Key    | Value                       |
|--------|-----------------------------|
| street | 1600+Pennsylvania+Avenue+NW |
| city   | Washington                  |
| state  | DC                          |
| zip    | 20500                       |

Usage #6 : Create a Multiplication Table

WITH Digits AS (
    SELECT CAST(value AS TINYINT) AS value
    FROM STRING_SPLIT('1,2,3,4,5', ',')
)
SELECT CONCAT(d1.value, ' * ', d2.value, ' = ', d1.value * d2.value) AS MultiplicationTable
FROM Digits d1 CROSS JOIN Digits d2;
| MultiplicationTable |
|---------------------|
| 1 * 1 = 1           |
| 1 * 2 = 2           |
| 1 * 3 = 3           |
| 1 * 4 = 4           |
| 1 * 5 = 5           |
| 2 * 1 = 2           |
| 2 * 2 = 4           |
| 2 * 3 = 6           |
| 2 * 4 = 8           |
| 2 * 5 = 10          |
| 3 * 1 = 3           |
| 3 * 2 = 6           |
| 3 * 3 = 9           |
| 3 * 4 = 12          |
| 3 * 5 = 15          |
| 4 * 1 = 4           |
| 4 * 2 = 8           |
| 4 * 3 = 12          |
| 4 * 4 = 16          |
| 4 * 5 = 20          |
| 5 * 1 = 5           |
| 5 * 2 = 10          |
| 5 * 3 = 15          |
| 5 * 4 = 20          |
| 5 * 5 = 25          |