SQL Server 2016 前無內建 STRING_SPLIT,且 STRING_SPLIT 不保證順序、無序號。自訂資料表值函數 (TVF) 可精確控制分割邏輯、保留順序、支援多字元分隔符,是 T-SQL 開發必備工具函數。
CREATE FUNCTION dbo.SplitStringToTable
(
@InputString NVARCHAR(MAX), -- 待分割字串
@Separator NVARCHAR(10) = N',' -- 分隔符,預設逗號
)
RETURNS @Result TABLE
(
Seq INT IDENTITY(1,1) PRIMARY KEY, -- 順序號
Value NVARCHAR(MAX) NOT NULL -- 分割後片段
)
AS
BEGIN
DECLARE @StartPos INT = 1,
@FindPos INT,
@Len INT = LEN(@InputString),
@SepLen INT = LEN(@Separator),
@Token NVARCHAR(MAX);
-- 空字串直接返回空表
IF @Len = 0 RETURN;
WHILE @StartPos <= @Len
BEGIN
-- 尋找下一個分隔符位置
SET @FindPos = CHARINDEX(@Separator, @InputString, @StartPos);
-- 找不到分隔符,取剩餘字串
IF @FindPos = 0
SET @FindPos = @Len + 1;
-- 擷取片段
SET @Token = SUBSTRING(@InputString, @StartPos, @FindPos - @StartPos);
-- 插入結果表
INSERT INTO @Result (Value) VALUES (@Token);
-- 移動起始位置 (跳過分隔符)
SET @StartPos = @FindPos + @SepLen;
END
RETURN;
END;
GO
-- 基本測試
SELECT * FROM dbo.SplitStringToTable(N'a,b,c,d,e,f,g', N',');
-- 多字元分隔符
SELECT * FROM dbo.SplitStringToTable(N'Apple||Orange||Banana', N'||');
-- 連續分隔符 (產生空字串)
SELECT * FROM dbo.SplitStringToTable(N'a,,b', N',');
-- 開頭/結尾分隔符
SELECT * FROM dbo.SplitStringToTable(N',a,b,', N',');
-- 無分隔符
SELECT * FROM dbo.SplitStringToTable(N'HelloWorld', N',');
-- 空字串
SELECT * FROM dbo.SplitStringToTable(N'', N',');
| Seq | Value |
|---|---|
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
| 5 | e |
| 6 | f |
| 7 | g |
CREATE FUNCTION dbo.SplitStringToTableEx
(
@InputString NVARCHAR(MAX),
@Separator NVARCHAR(10) = N',',
@TrimSpace BIT = 1, -- 是否 TRIM 空白
@RemoveEmpty BIT = 1 -- 是否移除空值
)
RETURNS @Result TABLE (Seq INT IDENTITY(1,1) PRIMARY KEY, Value NVARCHAR(MAX) NOT NULL)
AS
BEGIN
DECLARE @StartPos INT = 1, @FindPos INT, @Len INT = LEN(@InputString), @SepLen INT = LEN(@Separator), @Token NVARCHAR(MAX);
IF @Len = 0 RETURN;
WHILE @StartPos <= @Len
BEGIN
SET @FindPos = CHARINDEX(@Separator, @InputString, @StartPos);
IF @FindPos = 0 SET @FindPos = @Len + 1;
SET @Token = SUBSTRING(@InputString, @StartPos, @FindPos - @StartPos);
IF @TrimSpace = 1 SET @Token = LTRIM(RTRIM(@Token));
IF @RemoveEmpty = 0 OR @Token <> N''
INSERT INTO @Result (Value) VALUES (@Token);
SET @StartPos = @FindPos + @SepLen;
END
RETURN;
END;
GO
| 方案 | 適用版本 | 效能 | 順序保證 | 備註 |
|---|---|---|---|---|
| 自訂 TVF (WHILE 迴圈) | All | 中 (RBAR) | ✅ | 邏輯彈性最高 |
STRING_SPLIT | 2016+ | 高 (原生) | ❌ | 無序號、單字元分隔符 |
STRING_SPLIT + ORDINAL | 2022+ | 高 | ✅ | enable_ordinal = 1 |
| XML 解析 | 2005+ | 低 | ✅ | 大字串記憶體壓力大 |
| JSON 解析 | 2016+ | 中高 | ✅ | 需轉 JSON 陣列格式 |
| Tally Table / Numbers Table | All | 高 (集合式) | ✅ | 無迴圈、效能最佳 |
CREATE FUNCTION dbo.SplitString_Tally
(
@InputString NVARCHAR(MAX),
@Separator NVARCHAR(10) = N','
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1), -- 2^1
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B), -- 2^2
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B), -- 2^4
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B), -- 2^8
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B), -- 2^16 = 65536
Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM L4),
Separators AS (
SELECT n AS Pos
FROM Nums
WHERE n <= LEN(@InputString)
AND SUBSTRING(@InputString, n, LEN(@Separator)) = @Separator
),
Tokens AS (
SELECT
ROW_NUMBER() OVER (ORDER BY Pos) AS Seq,
SUBSTRING(
@InputString,
CASE WHEN LAG(Pos) OVER (ORDER BY Pos) IS NULL THEN 1
ELSE LAG(Pos) OVER (ORDER BY Pos) + LEN(@Separator) END,
CASE WHEN Pos IS NULL THEN LEN(@InputString) + 1
ELSE Pos END
- CASE WHEN LAG(Pos) OVER (ORDER BY Pos) IS NULL THEN 1
ELSE LAG(Pos) OVER (ORDER BY Pos) + LEN(@Separator) END
) AS Value
FROM (
SELECT Pos FROM Separators
UNION ALL SELECT LEN(@InputString) + 1
) AS AllPos(Pos)
)
SELECT Seq, Value FROM Tokens WHERE Value <> N'' OR @InputString = N'';
GO
WHERE ID IN (SELECT Value FROM dbo.SplitStringToTable(@Ids, ','))BULK INSERT 逐行分割根據 SQL Server 版本與效能需求選擇:2022+ 用 STRING_SPLIT(..., enable_ordinal=1);2016-2019 用 Tally Table TVF;舊版用 WHILE TVF 或 XML。自訂 TVF 邏輯可控、好維護,是 T-SQL 開發工具箱核心元件。