好的,我找到了一种方法来完成我所追求的。系好安全带,这会变得颠簸。
所以基本的问题是获取具有两种分隔符的字符串:条目和值。每个条目代表一组值,我想将字符串转换为一个表格,每个条目的每个值对应一列。我试图使它成为一个 UDF,但临时表和动态 SQL 的必要性意味着它必须是一个存储过程。
CREATE PROCEDURE [dbo].[ParseValueList]
(
@parseString varchar(8000),
@itemDelimiter CHAR(1),
@valueDelimiter CHAR(1)
)
AS
BEGIN
SET NOCOUNT ON;
IF object_id('tempdb..#ParsedValues') IS NOT NULL
BEGIN
DROP TABLE #ParsedValues
END
CREATE TABLE #ParsedValues
(
EntryID int,
[Rank] int,
Pair varchar(200)
)
所以这只是基本设置,建立临时表来保存我的中间结果。
;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),--Brute forces 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --Uses a cross join to generate 100 rows (10 * 10)
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --Uses a cross join to generate 10,000 rows (100 * 100)
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E4)
那段漂亮的 SQL 来自 SQL Server Central's Forums,被称为“大师”。这是一个很棒的小 10,000 行计数表,非常适合字符串拆分。
INSERT INTO #ParsedValues
SELECT ItemNumber AS EntryID, ROW_NUMBER() OVER (PARTITION BY ItemNumber ORDER BY ItemNumber) AS [Rank],
SUBSTRING(Items.Item, T1.N, CHARINDEX(@valueDelimiter, Items.Item + @valueDelimiter, T1.N) - T1.N) AS [Value]
FROM(
SELECT ROW_NUMBER() OVER (ORDER BY T2.N) AS ItemNumber,
SUBSTRING(@parseString, T2.N, CHARINDEX(@itemDelimiter, @parseString + @itemDelimiter, T2.N) - T2.N) AS Item
FROM cteTally T2
WHERE T2.N < LEN(@parseString) + 2 --Ensures we cut out once the entire string is done
AND SUBSTRING(@itemDelimiter + @parseString, T2.N, 1) = @itemDelimiter
) AS Items, cteTally T1
WHERE T1.N < LEN(@parseString) + 2 --Ensures we cut out once the entire string is done
AND SUBSTRING(@valueDelimiter + Items.Item, T1.N, 1) = @valueDelimiter
好的,这是第一个真正密集的肉质部分。内部选择正在使用 guru 的字符串拆分方法沿项目分隔符(逗号)拆分我的字符串。然后将该表传递给执行相同操作的外部选择,但这次使用值分隔符(冒号)到每一行。内部 RowNumber (EntryID) 和外部 RowNumber over Partition (Rank) 是枢轴的关键。 EntryID 显示值属于哪个 Item,Rank 显示值的序号。
DECLARE @columns varchar(200)
DECLARE @columnNames varchar(2000)
DECLARE @query varchar(8000)
SELECT @columns = COALESCE(@columns + ',[' + CAST([Rank] AS varchar) + ']', '[' + CAST([Rank] AS varchar)+ ']'),
@columnNames = COALESCE(@columnNames + ',[' + CAST([Rank] AS varchar) + '] AS Value' + CAST([Rank] AS varchar)
, '[' + CAST([Rank] AS varchar)+ '] AS Value' + CAST([Rank] AS varchar))
FROM (SELECT DISTINCT [Rank] FROM #ParsedValues) AS Ranks
SET @query = '
SELECT '+ @columnNames +'
FROM #ParsedValues
PIVOT
(
MAX([Value]) FOR [Rank]
IN (' + @columns + ')
) AS pvt'
EXECUTE(@query)
DROP TABLE #ParsedValues
END
最后,使之成为可能的动态 sql。通过获取 Distinct Ranks 列表,我们设置了列列表。然后将其写入动态轴,该轴将值倾斜并将每个值插入正确的列,每个列都有一个通用的“Value#”标题。
因此,通过使用格式正确的值字符串调用EXEC ParseValueList,我们可以将其分解成一个表格来满足我们的目的!它适用于简单的键:值对(但可能是矫枉过正),并且可以扩展到相当多的列(我认为最多大约 50 列,但这真的很愚蠢。)
无论如何,希望对遇到类似问题的人有所帮助。
(是的,它可能也可以在 SQLCLR 之类的东西中完成,但我发现用纯 SQL 解决问题非常高兴。)