需要将列拆分为行和列
Posted
技术标签:
【中文标题】需要将列拆分为行和列【英文标题】:Need to split column into rows and columns 【发布时间】:2020-10-02 10:05:25 【问题描述】:我有一张这样的桌子:
ID cst
1 string1;3;string2;string3;34;string4;-1;string5;string6;12;string7;5;string8,string9, 65
2 string10;-3;string11;string12;56;string13;6;string14;string15;9
etc.
现在我想将 cst 列拆分为 5 列和多行。 像这样:
ID C1 C2 C3 C4 C5
1 string1 3 string2 string3 34
1 string4 -1 string5 string6 12
1 string7 5 string8 string9 65
2 string10 -3 string11 string12 56
2 string13 6 string14 string15 9
etc.
如何做到这一点?我在 SQL-server 2017 上,所以我可以使用 string_split 函数。这个函数的问题是它只产生一个输出列...
我希望你创建一个输出表格的 UDF。该函数将使用这些输入参数:字符串、分隔符、列数。因此,该函数可以动态地与不同数量的列一起使用。
ps。字符串当然可以是可变长度的。
【问题讨论】:
STRING_SPLIT()
不保证以预期的排序顺序返回。特别是在位置很重要的情况下,它相当无用......
【参考方案1】:
试试这个:
提示:您的示例数据中有一些“正常”逗号。 我怀疑这些是错误的并使用了分号。 如果这是错误的,您可以使用一般的 REPLACE() 来使用“;”而不是“,”。
创建一个声明的表来模拟您的问题
DECLARE @tbl TABLE(ID INT, cst VARCHAR(1000));
INSERT INTO @tbl(ID,cst)
VALUES(1,'string1;3;string2;string3;34;string4;-1;string5;string6;12;string7;5;string8;string9; 65')
,(2,'string10;-3;string11;string12;56;string13;6;string14;string15;9');
--查询(对于几乎所有版本的 SQL-Server,请在下面找到 v2017+ 作为 UPDATE)
WITH cte AS
(
SELECT t.ID
,B.Nr
,A.Casted.value('(/x[sql:column("B.Nr")]/text())[1]','varchar(max)') AS ValueAtPosition
,(B.Nr-1) % 5 AS Position
,(B.Nr-1)/5 AS GroupingKey
FROM @tbl t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.cst,';','</x><x>') + '</x>' AS XML)) A(Casted)
CROSS APPLY(SELECT TOP(A.Casted.value('count(x)','int')) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) FROM master..spt_values) B(Nr)
)
SELECT ID
,GroupingKey
,MAX(CASE WHEN Position=0 THEN ValueAtPosition END) AS C1
,MAX(CASE WHEN Position=1 THEN ValueAtPosition END) AS C2
,MAX(CASE WHEN Position=2 THEN ValueAtPosition END) AS C3
,MAX(CASE WHEN Position=3 THEN ValueAtPosition END) AS C4
,MAX(CASE WHEN Position=4 THEN ValueAtPosition END) AS C5
FROM cte
GROUP BY ID,GroupingKey
ORDER BY ID,GroupingKey;
简而言之:
我们使用APPLY
将转换为XML 的字符串添加到结果集中。这将有助于拆分字符串 ("a;b;c" => <x>a</x><x>b</x><x>c</x>
)
我们使用另一个APPLY
来创建一个动态提示,其中包含一个计算出的TOP
子句。它将返回与 XML 中的元素一样多的虚拟行
我们使用sql:column()
根据每个元素的位置和一些简单的数学运算来获取每个元素的值,以创建分组键和从 0 到 4 的编号等。
我们使用GROUP BY
和MAX(CASE...)
将值放置在拟合列中(老式枢轴 或条件聚合)。
提示:如果你想要这个完全通用的,有很多列是事先不知道的。您不能使用任何类型的函数或临时查询。您宁愿需要在存储过程中创建某种动态语句以及 EXEC
。
老实说:这可能是 XY 问题。这种方法是错误的想法——至少在我能想到的几乎所有情况下都是这样。
SQL-Server 2017+ 更新
您使用的是 v2017,这允许使用 JSON,这在 位置安全 字符串拆分方面要快一些。试试这个:
SELECT t.ID
,A.*
FROM @tbl t
CROSS APPLY OPENJSON(CONCAT('["',REPLACE(t.cst,';','","'),'"]')) A
总体思路是一样的。我们将字符串转换为 JSON 数组 ("a,b,c" => ["a","b","c"]) 并使用 APPLY OPENJSON()
读取它。
您可以在“关键”列执行相同的数学运算,然后按照上述方式完成其余的操作。
正因为这里已经准备好了,所以这是v2017+的完整查询
WITH cte AS
(
SELECT t.ID
,A.[key]+1 AS Nr
,A.[value] AS ValueAtPosition
,A.[key] % 5 AS Position
,A.[key]/5 AS GroupingKey
FROM @tbl t
CROSS APPLY OPENJSON(CONCAT('["',REPLACE(t.cst,';','","'),'"]')) A
)
SELECT ID
,GroupingKey
,MAX(CASE WHEN Position=0 THEN ValueAtPosition END) AS C1
,MAX(CASE WHEN Position=1 THEN ValueAtPosition END) AS C2
,MAX(CASE WHEN Position=2 THEN ValueAtPosition END) AS C3
,MAX(CASE WHEN Position=3 THEN ValueAtPosition END) AS C4
,MAX(CASE WHEN Position=4 THEN ValueAtPosition END) AS C5
FROM cte
GROUP BY ID,GroupingKey
ORDER BY ID,GroupingKey;
【讨论】:
嗨,Shnugo,效果很好。非常感谢你。【参考方案2】:老实说,这里最简单的选择可能是以下步骤:
-
将当前表写入 CSV 平面文件,使用分号作为分隔符(这也是当前
cst
列的分隔符
然后使用 SQL Server 的批量加载工具加载 CSV,再次使用分号作为列分隔符。这将生成一个包含 16 列的表,ID
,然后是 C1
到 C15
,包括 C15
。
新建表(ID, C1, C2, C3, C4, C5)
然后使用以下方法填充上表:
INSERT INTO newTable (ID, C1, C2, C3, C4, C5)
SELECT ID, C1, C2, C3, C4, C5 FROM loadedTable UNION ALL
SELECT ID, C6, C7, C8, C9, C10 FROM loadedTable UNION ALL
SELECT ID, C11, C12, C13, C14, C15 FROM loadedTable;
虽然上述建议似乎需要做很多工作,但 SQL Server 对正则表达式和复杂的字符串拆分操作的支持很差,尤其是在早期版本上。直接使用您当前的表可能是不可能的,或者比上面的工作更多。
【讨论】:
以上是关于需要将列拆分为行和列的主要内容,如果未能解决你的问题,请参考以下文章