再談SQL Server字串拆分與表格分列
阿新 • • 發佈:2018-12-08
字串拆分函式
剛工作那會寫了一篇關於字串拆分的文章,那時僅僅是考慮實現就可以了,沒考慮效能、簡潔等因素,現總結一下常用方法以及優劣。
為了考慮程式碼的可讀性和複用性,一般用函式將實現細節封裝,下面介紹幾種常用的方法:
迴圈拆分實現
CREATE FUNCTION [dbo].[SplitString]
(
@str NVARCHAR(4000)
,@char NVARCHAR(10) = ','
)
RETURNS @SplitStr TABLE
(
ID int IDENTITY PRIMARY KEY
,Value nvarchar(2000)
)
AS
BEGIN
SET @str = @str + @char
WHILE LEN(@str) > 0
BEGIN
INSERT @SplitStr
SELECT SUBSTRING(@str, 1, CHARINDEX(@char, @str) - 1)
SELECT @str = RIGHT(@str, LEN(@str) - CHARINDEX(@char, @str) - (LEN(@char) - 1))
END
RETURN
END
以上方法從SQL SERVER 2000時代就可以使用,可以說是最具版本相容性的寫法。
遞迴函式實現
CREATE FUNCTION [dbo].[SplitString_CTE]
(
@String NVARCHAR(4000),
@Delimiter NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(StartPos,EndPos)
AS(
SELECT 0 AS StartPos, CHARINDEX(@Delimiter,@String ) AS EndPos
UNION ALL
SELECT EndPos+LEN(@Delimiter), CHARINDEX(@Delimiter,@String,EndPos+LEN(@Delimiter))
FROM Split
WHERE EndPos > 0
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ID
,SUBSTRING(@String,StartPos,COALESCE(NULLIF(EndPos,0),LEN(@String)+1)-StartPos) AS Value
FROM Split
--OPTION(MAXRECURSION 0) 這裡不能使用,要放在最終查詢中使用
)
從SQL SERVER 2005 開始,出現了公共表示式這一利器,可以實現遞迴查詢,於有了上面的版本。
基本思路是從上一次查詢分隔符的位置加1開始成為新的起點,一直遞迴到找不到分隔符為止。
CTE的結果是每個分隔符號的起始位置,最外層查詢是利用起始位置查詢出最終結果。
XML實現
CREATE FUNCTION [dbo].[SplitString_XML]
(
@String NVARCHAR(4000),
@Delimiter NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS ID,b.Value
FROM
(
SELECT CAST(('<V>'+REPLACE(@String,@Delimiter,'</V><V>')+'</V>') AS XML) AS XMLString
) AS a
CROSS APPLY
(
SELECT N.value('.', 'nvarchar(10)') AS Value
FROM a.XMLString.nodes('V') As T(N)
) AS b
)
此方法通過構造XML字串去使用XML的特有方法進行實現。
結果展示
SELECT * FROM [dbo].[SplitString]('12,123,21312',',')
SELECT * FROM [dbo].[SplitString_CTE]('12,123,21312',',')
SELECT * FROM [dbo].[SplitString_XML]('12,123,21312',',')
均得到了正確的結果:
效能提示
通過執行計劃評估以及時間統計,發現CTE版本的效能最好。但如果分隔的專案太多,可能會超過SQL Server預設的最大100層遞迴,需要在查詢後面加上OPTION(MAXRECURSION 0)以防止報錯。
常用的迴圈法效能次之,主要消耗在每次的表變更插入上。
XML的效能最差,不建議使用。
分列
有了以上對單個字串進行拆分的函式,可以利用它們進行表格分列。
如下,左邊的列表,想要變為右邊的列表。
選擇以上函式中的一種實現,程式碼如下:
-- 生成測試資料
CREATE TABLE Test_TB(Title NVARCHAR(200))
INSERT Test_TB VALUES ('AA-BB-CC'),('CC-DD-EE'),('XX-YY'),('YY-ZZ')
-- 分列轉換
SELECT Title,[1],[2],[3]
FROM (SELECT a.Title,b.ID,b.Value
FROM Test_TB a
CROSS APPLY [dbo].[SplitString](a.Title,'-') AS b
) AS a
PIVOT(MAX(Value) FOR ID IN ([1],[2],[3])) AS b
結果如下:
後話
最後說一點題外話,SQL Server 2016對字串的拆分和合並已經有了內建的系統函式STRING_SPLIT和STRING_AGG。以後再也沒有自己實現的樂趣了,感慨程式語言越來越強大,輪子越來越多,多年以後可能傻瓜都會程式設計了,程式設計將和英語一樣成為一門多數人都要會的通用技能。