IF EXISTS (SELECT 1 FROM sysobjects WHERE id = OBJECT_ID('SSC_Multi_Split') AND OBJECTPROPERTY(id, 'IsProcedure') = 1)BEGIN DROP PROCEDURE SSC_Multi_Split;ENDGOCREATE PROCEDURE SSC_Multi_Split (@string VARCHAR(8000), @rowDeliminater CHAR(1), @colDeliminater CHAR(1))ASBEGIN IF object_id('tempdb..#temporaryResultHolder') IS NOT NULL BEGIN DROP TABLE #temporaryResultHolder; END; WITH CTE1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), CTE2(N) AS (SELECT 1 FROM CTE1 X CROSS JOIN CTE1 Y), CTE3(N) AS (SELECT 1 FROM CTE2 X CROSS JOIN CTE2 Y), CTE4(N) AS (SELECT 1 FROM CTE3 X CROSS JOIN CTE3 Y), CTE5(N) AS (SELECT 1 FROM CTE4 X CROSS JOIN CTE4 Y), CTE6(N) AS (SELECT 1 FROM CTE5 X CROSS JOIN CTE5 Y), TALLY(N) AS (SELECT TOP (ISNULL(DATALENGTH(@string),0)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM CTE6), RowStart(start) AS (SELECT 1 UNION ALL SELECT N+1 FROM TALLY WHERE SUBSTRING(@string,N,1) = @rowDeliminater ), RowLen(start,size) AS (SELECT start, ISNULL(NULLIF(CHARINDEX(@rowDeliminater,@string,start),0)-start,ISNULL(DATALENGTH(@string),8000)) FROM RowStart ), RowData(rowNumber,data) AS (SELECT ROW_NUMBER() OVER(ORDER BY start), SUBSTRING(@string, start, size) FROM RowLen ), ColumnStart(start, rowNumber) AS (SELECT b.N, b.rowNumber FROM (SELECT N+1, rowNumber FROM TALLY CROSS APPLY (SELECT rowNumber,data FROM RowData) b WHERE SUBSTRING(data,N,1) = @colDeliminater )a(N, rowNumber) CROSS APPLY (SELECT 1, rowNumber UNION ALL SELECT N, rowNumber)b(N, rowNumber) ), ColumnLen(rowNumber,start,size,data) AS (SELECT b.rowNumber, start, ISNULL(NULLIF(CHARINDEX(@colDeliminater,data,start),0)-start,ISNULL(DATALENGTH(data),8000)), data FROM ColumnStart a CROSS APPLY (SELECT rowNumber,data FROM RowData WHERE a.rowNumber = rowNumber) b ), allData(rowNumber,columnNumber,data) AS (SELECT rowNumber, ROW_NUMBER() OVER(PARTITION BY rowNumber ORDER BY start), SUBSTRING(data, start, size) FROM ColumnLen ) SELECT rowNumber,columnNumber,data INTO #temporaryResultHolder FROM allData; DECLARE @SQL NVARCHAR(MAX); WITH CTE1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), CTE2(N) AS (SELECT 1 FROM CTE1 X CROSS JOIN CTE1 Y), CTE3(N) AS (SELECT 1 FROM CTE2 X CROSS JOIN CTE2 Y), CTE4(N) AS (SELECT 1 FROM CTE3 X CROSS JOIN CTE3 Y), CTE5(N) AS (SELECT 1 FROM CTE4 X CROSS JOIN CTE4 Y), CTE6(N) AS (SELECT 1 FROM CTE5 X CROSS JOIN CTE5 Y), TALLY(N) AS (SELECT TOP (ISNULL(DATALENGTH(@string),0)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM CTE6) SELECT @SQL = STUFF((SELECT ','+CHAR(13)+CHAR(10)+'MAX(CASE WHEN columnNumber = '+CAST(N AS VARCHAR(20))+' THEN data ELSE '+CHAR(39)+CHAR(39)+' END) AS field'+CAST(N AS VARCHAR(20)) FROM TALLY CROSS APPLY (SELECT TOP 1 columnNumber FROM #temporaryResultHolder ORDER BY columnNumber DESC) b WHERE columnNumber >= N FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,3,''); SELECT @SQL = 'SELECT '+@SQL+CHAR(13)+CHAR(10)+'FROM #temporaryResultHolder'+CHAR(13)+CHAR(10)+'GROUP BY rowNumber'+CHAR(13)+CHAR(10)+'ORDER BY rowNumber'; EXECUTE sp_executeSQL @SQL;ENDGO
EXECUTE SSC_Multi_Split '30;38469|31;38470|32;38471|33;38472|34;38473|35;38474|36;38475|37;38476|38;38477|','|',';';
field1 field2------ -------30 3846931 3847032 3847133 3847234 3847335 3847436 3847537 3847638 38477
declare @TestString varchar(8000) = '30;38469|31;38470|32;38471|33;38472|34;38473|35;38474|36;38475|37;38476|38;38477|';select max(case ds2.ItemNumber when 1 then ds2.Item else '' end) as field1, max(case ds2.ItemNumber when 2 then ds2.Item else '' end) as field2from dbo.DelimitedSplit8K(@TestString,'|') ds1 cross apply dbo.DelimitedSplit8K(ds1.Item,';') ds2where ds1.Item <> ''group by ds1.ItemNumber;
IF EXISTS (SELECT 1 FROM sysobjects WHERE id = OBJECT_ID('DelimitedSplit8K'))BEGIN DROP FUNCTION [dbo].[DelimitedSplit8K];ENDGOCREATE FUNCTION [dbo].[DelimitedSplit8K]--===== Define I/O parameters (@pString VARCHAR(8000), @pDelimiter CHAR(1))--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!RETURNS TABLE WITH SCHEMABINDING AS RETURN--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000... -- enough to cover VARCHAR(8000) WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ), --10E+1 or 10 rows E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front -- for both a performance gain and prevention of accidental "overruns" SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 ),cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) SELECT 1 UNION ALL SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter ),cteLen(N1,L1) AS(--==== Return start and length (for use in substring) SELECT s.N1, ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000) FROM cteStart s )--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1), Item = SUBSTRING(@pString, l.N1, l.L1) FROM cteLen l;GO
IF EXISTS (SELECT 1 FROM sysobjects WHERE id = OBJECT_ID('SSC_Multi_Split'))BEGIN DROP PROCEDURE SSC_Multi_Split;ENDGOCREATE PROCEDURE SSC_Multi_Split (@string VARCHAR(8000), @rowDeliminater CHAR(1), @colDeliminater CHAR(1))ASBEGIN IF object_id('tempdb..#temporaryResultHolder') IS NOT NULL BEGIN DROP TABLE #temporaryResultHolder; END; SELECT ds1.ItemNumber AS rowNumber, ds2.ItemNumber AS columnNumber, ds2.Item AS data INTO #temporaryResultHolder FROM dbo.DelimitedSplit8K(@string, @rowDeliminater) ds1 CROSS APPLY dbo.DelimitedSplit8K(ds1.Item, @colDeliminater) ds2 WHERE ds1.Item <> ''; DECLARE @SQL NVARCHAR(MAX); WITH CTE1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), CTE2(N) AS (SELECT 1 FROM CTE1 X CROSS JOIN CTE1 Y), CTE3(N) AS (SELECT 1 FROM CTE2 X CROSS JOIN CTE2 Y), CTE4(N) AS (SELECT 1 FROM CTE3 X CROSS JOIN CTE3 Y), CTE5(N) AS (SELECT 1 FROM CTE4 X CROSS JOIN CTE4 Y), CTE6(N) AS (SELECT 1 FROM CTE5 X CROSS JOIN CTE5 Y), TALLY(N) AS (SELECT TOP (ISNULL(DATALENGTH(@string),0)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM CTE6) SELECT @SQL = STUFF((SELECT ','+CHAR(13)+CHAR(10)+'MAX(CASE WHEN columnNumber = '+CAST(N AS VARCHAR(20))+' THEN data ELSE '+CHAR(39)+CHAR(39)+' END) AS field'+CAST(N AS VARCHAR(20)) FROM TALLY CROSS APPLY (SELECT TOP 1 columnNumber FROM #temporaryResultHolder ORDER BY columnNumber DESC) b WHERE columnNumber >= N FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,3,''); SELECT @SQL = 'SELECT '+@SQL+CHAR(13)+CHAR(10)+'FROM #temporaryResultHolder'+CHAR(13)+CHAR(10)+'GROUP BY rowNumber'+CHAR(13)+CHAR(10)+'ORDER BY rowNumber'; EXECUTE sp_executeSQL @SQL;ENDGO
DECLARE @exampleData VARCHAR(8000) = '30;38469|31;38469|32;38469|33;38469|34;38469|';SET @exampleData = REPLACE(REPLACE(@exampleData,CHAR(13),''),CHAR(10),'');EXECUTE SSC_Multi_Split @exampleData,'|',';'