1. 程式人生 > >再談SQL Server字串拆分與表格分列

再談SQL Server字串拆分與表格分列

字串拆分函式

剛工作那會寫了一篇關於字串拆分的文章,那時僅僅是考慮實現就可以了,沒考慮效能、簡潔等因素,現總結一下常用方法以及優劣。
為了考慮程式碼的可讀性和複用性,一般用函式將實現細節封裝,下面介紹幾種常用的方法:

迴圈拆分實現

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_SPLITSTRING_AGG。以後再也沒有自己實現的樂趣了,感慨程式語言越來越強大,輪子越來越多,多年以後可能傻瓜都會程式設計了,程式設計將和英語一樣成為一門多數人都要會的通用技能。